From 36e04fba9526213b1c59f3ecefd1e81ec39c7d9c Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Sat, 25 May 2019 11:53:02 +0200 Subject: [PATCH 0001/1254] DS-4260: Remove non-existing command from oai harvester's help --- .../src/main/java/org/dspace/app/harvest/Harvest.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java index 3b324f2763..0ae992d26e 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java @@ -60,7 +60,6 @@ public class Harvest { options.addOption("p", "purge", false, "delete all items in the collection"); options.addOption("r", "run", false, "run the standard harvest procedure"); options.addOption("g", "ping", false, "test the OAI server and set"); - options.addOption("o", "once", false, "run the harvest procedure with specified parameters"); options.addOption("s", "setup", false, "Set the collection up for harvesting"); options.addOption("S", "start", false, "start the harvest loop"); options.addOption("R", "reset", false, "reset harvest status on all collections"); @@ -97,9 +96,6 @@ public class Harvest { HelpFormatter myhelp = new HelpFormatter(); myhelp.printHelp("Harvest\n", options); System.out.println("\nPING OAI server: Harvest -g -a oai_source -i oai_set_id"); - System.out.println( - "RUNONCE harvest with arbitrary options: Harvest -o -e eperson -c collection -t harvest_type -a " + - "oai_source -i oai_set_id -m metadata_format"); System.out.println( "SETUP a collection for harvesting: Harvest -s -c collection -t harvest_type -a oai_source -i " + "oai_set_id -m metadata_format"); @@ -125,9 +121,6 @@ public class Harvest { if (line.hasOption('g')) { command = "ping"; } - if (line.hasOption('o')) { - command = "runOnce"; - } if (line.hasOption('S')) { command = "start"; } @@ -246,6 +239,10 @@ public class Harvest { } pingResponder(oaiSource, oaiSetID, metadataKey); + } else { + System.out.println("Error - your command '" + command + "' was not recoginzed properly"); + System.out.println(" (run with -h flag for details)"); + System.exit(1); } } From a3f8503e9b33d0b3a39dd64c10561bc67d3de5a0 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 26 Jul 2019 11:27:37 -0400 Subject: [PATCH 0002/1254] [DS-4118] Use the correct leading character for a "set" directive --- dspace/config/emails/subscription | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/emails/subscription b/dspace/config/emails/subscription index 58a29c25ab..2879e57907 100644 --- a/dspace/config/emails/subscription +++ b/dspace/config/emails/subscription @@ -4,7 +4,7 @@ ## 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') +#set($subject = 'DSpace Subscription') New items are available in the collections you have subscribed to: ${params[0]} From 3441a0c9e24b1fe57d901ed61fd4b4619a9221d5 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 26 Jul 2019 11:50:22 -0400 Subject: [PATCH 0003/1254] [DS-4118] Don't require editing email templates at every site. --- dspace/config/dspace.cfg | 3 +++ dspace/config/emails/change_password | 2 +- dspace/config/emails/register | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4bb23f5db3..3a8f89a62f 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -182,6 +182,9 @@ mail.allowed.referrers = ${dspace.hostname} mail.message.headers = subject mail.message.headers = charset +# Helpdesk telephone. Not email, but should be with other contact info. +telephone.helpdesk = +1 555 555 5555 + ##### Asset Storage (bitstreams / files) ###### # Moved to config/spring/api/bitstore.xml diff --git a/dspace/config/emails/change_password b/dspace/config/emails/change_password index 6319d8b736..93778641e7 100644 --- a/dspace/config/emails/change_password +++ b/dspace/config/emails/change_password @@ -11,6 +11,6 @@ below: ${params[0]} If you need assistance with your account, please email -${config.get("mail.admin")} or call us at xxx-555-xxxx. +${config.get("mail.helpdesk")} or call us at ${config.get("telephone.helpdesk")}. The DSpace Team diff --git a/dspace/config/emails/register b/dspace/config/emails/register index 1e60899d45..331015dc16 100644 --- a/dspace/config/emails/register +++ b/dspace/config/emails/register @@ -11,6 +11,6 @@ below: ${params[0]} If you need assistance with your account, please email -${config.get("mail.admin")} or call us at xxx-555-xxxx. +${config.get("mail.helpdesk")} or call us at ${config.get("telephone.helpdesk")}. The DSpace Team From d1d3f4f73d91bee10c5f20422a77573cd59d93e1 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 5 Feb 2020 10:40:53 -0500 Subject: [PATCH 0004/1254] [DS-4118] Make new helpdesk phone property optional and give it a better name. --- dspace/config/dspace.cfg | 4 ++-- dspace/config/emails/change_password | 8 +++++++- dspace/config/emails/register | 8 +++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 3a8f89a62f..f73f88dcbe 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -182,8 +182,8 @@ mail.allowed.referrers = ${dspace.hostname} mail.message.headers = subject mail.message.headers = charset -# Helpdesk telephone. Not email, but should be with other contact info. -telephone.helpdesk = +1 555 555 5555 +# Helpdesk telephone. Not email, but should be with other contact info. Optional. +#mail.message.helpdesk.telephone = +1 555 555 5555 ##### Asset Storage (bitstreams / files) ###### # Moved to config/spring/api/bitstore.xml diff --git a/dspace/config/emails/change_password b/dspace/config/emails/change_password index 93778641e7..eb114feeeb 100644 --- a/dspace/config/emails/change_password +++ b/dspace/config/emails/change_password @@ -5,12 +5,18 @@ ## See org.dspace.core.Email for information on the format of this file. ## #set($subject = 'Change Password Request') +#set($phone = ${config.get('mail.message.helpdesk.telephone')}) To change the password for your DSpace account, please click the link below: ${params[0]} If you need assistance with your account, please email -${config.get("mail.helpdesk")} or call us at ${config.get("telephone.helpdesk")}. + + ${config.get("mail.helpdesk")} +#if( $phone ) + +or call us at ${phone}. +#end The DSpace Team diff --git a/dspace/config/emails/register b/dspace/config/emails/register index 331015dc16..694be449a8 100644 --- a/dspace/config/emails/register +++ b/dspace/config/emails/register @@ -5,12 +5,18 @@ ## See org.dspace.core.Email for information on the format of this file. ## #set($subject = "${config.get('dspace.name')} Account Registration") +#set($phone = ${config.get('mail.message.helpdesk.telephone')}) To complete registration for a DSpace account, please click the link below: ${params[0]} If you need assistance with your account, please email -${config.get("mail.helpdesk")} or call us at ${config.get("telephone.helpdesk")}. + + ${config.get("mail.helpdesk")} +#if( $phone ) + +or call us at ${phone}. +#end The DSpace Team From db09fac2160439af190b960ae07425ad37998373 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Mon, 2 Mar 2020 17:09:06 +0100 Subject: [PATCH 0005/1254] Add StripDiacritics to OrderFormat classes for author and text and normalize the starts_with param text submitted --- .../java/org/dspace/browse/BrowseEngine.java | 2 +- .../org/dspace/sort/OrderFormatAuthor.java | 2 + .../java/org/dspace/sort/OrderFormatText.java | 2 + .../app/rest/BrowsesResourceControllerIT.java | 152 ++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index 302d46eb0d..6e0a94f52f 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -381,7 +381,7 @@ public class BrowseEngine { // this is the distinct table constrained to either community or collection dao.setTable(browseIndex.getDistinctTableName()); - dao.setStartsWith(StringUtils.lowerCase(scope.getStartsWith())); + dao.setStartsWith(normalizeJumpToValue(scope.getStartsWith())); // remind the DAO that this is a distinct value browse, so it knows what sort // of query to build dao.setDistinct(true); diff --git a/dspace-api/src/main/java/org/dspace/sort/OrderFormatAuthor.java b/dspace-api/src/main/java/org/dspace/sort/OrderFormatAuthor.java index a38530606e..d0fdeba112 100644 --- a/dspace-api/src/main/java/org/dspace/sort/OrderFormatAuthor.java +++ b/dspace-api/src/main/java/org/dspace/sort/OrderFormatAuthor.java @@ -9,6 +9,7 @@ package org.dspace.sort; import org.dspace.text.filter.DecomposeDiactritics; import org.dspace.text.filter.LowerCaseAndTrim; +import org.dspace.text.filter.StripDiacritics; import org.dspace.text.filter.TextFilter; /** @@ -19,6 +20,7 @@ import org.dspace.text.filter.TextFilter; public class OrderFormatAuthor extends AbstractTextFilterOFD { { filters = new TextFilter[] {new DecomposeDiactritics(), + new StripDiacritics(), new LowerCaseAndTrim()}; } } diff --git a/dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java b/dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java index 403034f675..5c415ad074 100644 --- a/dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java +++ b/dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java @@ -8,6 +8,7 @@ package org.dspace.sort; import org.dspace.text.filter.DecomposeDiactritics; +import org.dspace.text.filter.StripDiacritics; import org.dspace.text.filter.LowerCaseAndTrim; import org.dspace.text.filter.TextFilter; @@ -19,6 +20,7 @@ import org.dspace.text.filter.TextFilter; public class OrderFormatText extends AbstractTextFilterOFD { { filters = new TextFilter[] {new DecomposeDiactritics(), + new StripDiacritics(), new LowerCaseAndTrim()}; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index b9d8a030d0..ef69c19228 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 @@ -709,6 +709,158 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe }; + @Test + public void testBrowseByEntriesStartsWithAndDiacritics() 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. 4 public items that are readable by Anonymous + Item item1 = ItemBuilder.createItem(context, col1) + .withTitle("Item1") + .withAuthor("Álvarez, Nombre") + .withIssueDate("1912-06-23") + .withSubject("Teléfono") + .build(); + + Item item2 = ItemBuilder.createItem(context, col1) + .withTitle("Item2") + .withAuthor("Ögren, Name") + .withIssueDate("1982-06-25") + .withSubject("Televisor") + .build(); + + Item item3 = ItemBuilder.createItem(context, col2) + .withTitle("Item3") + .withAuthor("Azuaga, Nombre") + .withIssueDate("1990") + .withSubject("Telecomunicaciones") + .build(); + + Item item4 = ItemBuilder.createItem(context, col2) + .withTitle("Item4") + .withAuthor("Alonso, Nombre") + .withAuthor("Ortiz, Nombre") + .withIssueDate("1995-05-23") + .withSubject("Guion") + .build(); + + + // ---- BROWSES BY ENTRIES ---- + + //** WHEN ** + //An anonymous user browses the entries in the Browse by Author endpoint + //with startsWith set to A + getClient().perform(get("/api/discover/browses/author/entries?startsWith=A") + .param("size", "4")) + + //** 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 3 elements + .andExpect(jsonPath("$.page.totalElements", is(3))) + //As entry browsing works as a filter, we expect to be on page 0 + .andExpect(jsonPath("$.page.number", is(0))) + + //Verify that the index filters to the "Alonso, Nombre", "Álvarez, Nombre" and "Azuaga, Nombre" + // and diacritics are ignored in sorting + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("Alonso, Nombre", 1), + BrowseEntryResourceMatcher.matchBrowseEntry("Álvarez, Nombre", 1), + BrowseEntryResourceMatcher.matchBrowseEntry("Azuaga, Nombre", 1) + ))) + + //Verify startsWith parameter is included in the links + .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=A"))); + + //** WHEN ** + //An anonymous user browses the entries in the Browse by Author endpoint + //with startsWith set to Ú (accented) + getClient().perform(get("/api/discover/browses/author/entries?startsWith=Ó")) + + //** 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 2 elements + .andExpect(jsonPath("$.page.totalElements", is(2))) + //As entry browsing works as a filter, we expect to be on page 0 + .andExpect(jsonPath("$.page.number", is(0))) + + //Verify that the index filters to the "Ögren, Name"" and "Ortiz, Nombre" + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("Ögren, Name", 1), + BrowseEntryResourceMatcher.matchBrowseEntry("Ortiz, Nombre", 1) + ))) + //Verify that the startsWith paramater is included in the links + .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=Ó"))); + + + //** WHEN ** + //An anonymous user browses the entries in the Browse by Subject endpoint + //with startsWith set to Cana + getClient().perform(get("/api/discover/browses/subject/entries?startsWith=Tele")) + + //** 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 3 elements + .andExpect(jsonPath("$.page.totalElements", is(3))) + //As entry browsing works as a filter, we expect to be on page 0 + .andExpect(jsonPath("$.page.number", is(0))) + + //Verify that the index filters to the "Telecomunicaciones', "Teléfono" and "Televisor" and + // it is sorted ignoring diacritics + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("Telecomunicaciones", 1), + BrowseEntryResourceMatcher.matchBrowseEntry("Teléfono", 1), + BrowseEntryResourceMatcher.matchBrowseEntry("Televisor", 1) + ))) + //Verify that the startsWith paramater is included in the links + .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=Tele"))); + + //** WHEN ** + //An anonymous user browses the entries in the Browse by Subject endpoint + //with startsWith set to Guión + getClient().perform(get("/api/discover/browses/subject/entries?startsWith=Guión")) + + //** 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 entry "Guion" to be present + .andExpect(jsonPath("$.page.totalElements", is(1))) + //As entry browsing works as a filter, we expect to be on page 0 + .andExpect(jsonPath("$.page.number", is(0))) + + //Verify that the index filters to the "Guion" + .andExpect(jsonPath("$._embedded.entries", + contains(BrowseEntryResourceMatcher.matchBrowseEntry("Guion", 1) + ))) + //Verify that the startsWith paramater is included in the links + .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=Guión"))); + + }; + @Test public void testBrowseByItemsStartsWith() throws Exception { context.turnOffAuthorisationSystem(); From c2df412e942748df37611fb8d256506a662942c6 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Mon, 2 Mar 2020 18:02:50 +0100 Subject: [PATCH 0006/1254] Remove unused import --- dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index 6e0a94f52f..0046f78b89 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; import org.dspace.content.Community; From 8fb214c061e2e8c63f4f15482529901e81ffd882 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Mon, 2 Mar 2020 18:46:55 +0100 Subject: [PATCH 0007/1254] change import order --- dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java b/dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java index 5c415ad074..e108328687 100644 --- a/dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java +++ b/dspace-api/src/main/java/org/dspace/sort/OrderFormatText.java @@ -8,8 +8,8 @@ package org.dspace.sort; import org.dspace.text.filter.DecomposeDiactritics; -import org.dspace.text.filter.StripDiacritics; import org.dspace.text.filter.LowerCaseAndTrim; +import org.dspace.text.filter.StripDiacritics; import org.dspace.text.filter.TextFilter; /** From 2a9eb2048bb2669473cb4981b5817cf83cb0c897 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 3 Jan 2020 12:05:10 -0500 Subject: [PATCH 0008/1254] [DS-4110] New email template sent to a newly-registered user. --- .../org/dspace/eperson/EPersonConsumer.java | 40 +++++++++++++++---- dspace/config/dspace.cfg | 3 ++ dspace/config/emails/welcome | 15 +++++++ 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 dspace/config/emails/welcome diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonConsumer.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonConsumer.java index 319dcad65c..373f7fee80 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonConsumer.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonConsumer.java @@ -7,12 +7,13 @@ */ package org.dspace.eperson; +import java.io.IOException; import java.util.Date; import java.util.UUID; import javax.mail.MessagingException; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Email; @@ -22,6 +23,8 @@ import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.event.Consumer; import org.dspace.event.Event; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; /** * Class for handling updates to EPersons @@ -29,16 +32,19 @@ import org.dspace.event.Event; * Recommended filter: EPerson+Create * * @author Stuart Lewis - * @version $Revision$ */ public class EPersonConsumer implements Consumer { /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(EPersonConsumer.class); + private static final Logger log + = org.apache.logging.log4j.LogManager.getLogger(EPersonConsumer.class); protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + protected ConfigurationService cfg + = DSpaceServicesFactory.getInstance().getConfigurationService(); + /** * Initalise the consumer * @@ -69,7 +75,8 @@ public class EPersonConsumer implements Consumer { case Constants.EPERSON: if (et == Event.CREATE) { // Notify of new user registration - String notifyRecipient = ConfigurationManager.getProperty("registration.notify"); + String notifyRecipient = cfg.getProperty("registration.notify"); + EPerson eperson = ePersonService.find(context, id); if (notifyRecipient == null) { notifyRecipient = ""; } @@ -77,13 +84,12 @@ public class EPersonConsumer implements Consumer { if (!notifyRecipient.equals("")) { try { - EPerson eperson = ePersonService.find(context, id); Email adminEmail = Email .getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), "registration_notify")); adminEmail.addRecipient(notifyRecipient); - adminEmail.addArgument(ConfigurationManager.getProperty("dspace.name")); - adminEmail.addArgument(ConfigurationManager.getProperty("dspace.ui.url")); + adminEmail.addArgument(cfg.getProperty("dspace.name")); + adminEmail.addArgument(cfg.getProperty("dspace.ui.url")); adminEmail.addArgument(eperson.getFirstName() + " " + eperson.getLastName()); // Name adminEmail.addArgument(eperson.getEmail()); adminEmail.addArgument(new Date()); @@ -99,6 +105,26 @@ public class EPersonConsumer implements Consumer { "error_emailing_administrator", ""), me); } } + + // If enabled, send a "welcome" message to the new EPerson. + if (cfg.getBooleanProperty("mail.welcome.enabled", false)) { + String addressee = eperson.getEmail(); + if (StringUtils.isNotBlank(addressee)) { + log.debug("Sending welcome email to {}", addressee); + try { + Email message = Email.getEmail( + I18nUtil.getEmailFilename(context.getCurrentLocale(), "welcome")); + message.addRecipient(addressee); + message.send(); + } catch (IOException | MessagingException ex) { + log.warn("Welcome message not sent to {}: {}", + addressee, ex.getMessage()); + } + } else { + log.warn("Welcome message not sent to EPerson {} because it has no email address.", + eperson.getID().toString()); + } + } } else if (et == Event.DELETE) { // TODO: Implement this if required } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 1bbd895131..e3ce2f426a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -128,6 +128,9 @@ alert.recipient = ${mail.admin} # Recipient for new user registration emails (defaults to unspecified) #registration.notify = +# Enable a "welcome letter" to the newly-registered user. +mail.welcome.enabled = false + # Set the default mail character set. This may be overridden by providing a line # inside the email template "charset: ", otherwise this default is used. mail.charset = UTF-8 diff --git a/dspace/config/emails/welcome b/dspace/config/emails/welcome new file mode 100644 index 0000000000..febc082e07 --- /dev/null +++ b/dspace/config/emails/welcome @@ -0,0 +1,15 @@ +## E-mail sent to a DSpace user after a new account is registered. +## +## See org.dspace.core.Email for information on the format of this file. +## +#set($subject = "Welcome new registered ${config.get('dspace.name')} user!") +Thank you for registering an account. Your new account can be used immediately +to subscribe to notices of new content arriving in collections of your choice. + +Your new account can also be granted privileges to submit new content, or to +edit and/or approve submissions. + +If you need assistance with your account, please email +${config.get("mail.admin")}. + +The ${config.get('dspace.name')} Team From 65f04fcec9390c6b4678039c63f535e0ed18e59d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 24 Nov 2020 10:55:08 -0500 Subject: [PATCH 0009/1254] First batch of errorprone fixes. #3061 --- .../org/dspace/authorize/ResourcePolicy.java | 1 + .../java/org/dspace/content/Bitstream.java | 16 +------ .../org/dspace/content/BitstreamFormat.java | 10 ++-- .../dspace/content/CollectionServiceImpl.java | 2 +- .../java/org/dspace/content/Community.java | 15 +----- .../dspace/content/CommunityServiceImpl.java | 2 +- .../main/java/org/dspace/content/DCDate.java | 2 +- .../java/org/dspace/content/DSpaceObject.java | 3 +- .../content/DSpaceObjectServiceImpl.java | 6 +-- .../dspace/content/InProgressSubmission.java | 2 +- .../main/java/org/dspace/content/Item.java | 19 ++------ .../org/dspace/content/MetadataField.java | 9 ++-- .../org/dspace/content/MetadataFieldName.java | 46 +++++++++---------- .../org/dspace/content/MetadataSchema.java | 2 +- .../org/dspace/content/MetadataValue.java | 8 ++-- .../content/service/BitstreamService.java | 1 + .../main/java/org/dspace/core/Context.java | 2 - .../main/java/org/dspace/core/I18nUtil.java | 14 ++---- .../src/main/java/org/dspace/core/Utils.java | 33 +++++++------ .../dspace/curate/AbstractCurationTask.java | 3 -- .../java/org/dspace/curate/CitationPage.java | 36 +++++++++------ .../java/org/dspace/curate/TaskResolver.java | 12 +++-- .../discovery/FullTextContentStreams.java | 16 ++++--- .../org/dspace/event/ConsumerProfile.java | 4 +- .../src/main/java/org/dspace/event/Event.java | 9 ++-- .../main/java/org/dspace/handle/Handle.java | 2 +- .../text/filter/InitialArticleWord.java | 7 +-- .../storedcomponents/InProgressUser.java | 1 + .../dspace/content/MetadataFieldNameTest.java | 12 ++--- 29 files changed, 134 insertions(+), 161 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java index b84055b8b0..d0605c31b3 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java @@ -169,6 +169,7 @@ public class ResourcePolicy implements ReloadableEntity { * * @return the internal identifier */ + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/content/Bitstream.java b/dspace-api/src/main/java/org/dspace/content/Bitstream.java index 39d78b0491..451a3b7578 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bitstream.java +++ b/dspace-api/src/main/java/org/dspace/content/Bitstream.java @@ -21,7 +21,6 @@ import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Transient; -import org.apache.log4j.Logger; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; import org.dspace.core.Constants; @@ -36,17 +35,10 @@ import org.hibernate.proxy.HibernateProxyHelper; * the contents of a bitstream; you need to create a new bitstream. * * @author Robert Tansley - * @version $Revision$ */ @Entity @Table(name = "bitstream") public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport { - - /** - * log4j logger - */ - private static Logger log = Logger.getLogger(Bitstream.class); - @Column(name = "bitstream_id", insertable = false, updatable = false) private Integer legacyId; @@ -411,7 +403,7 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport */ @Override public boolean equals(Object other) { - if (other == null) { + if (!(other instanceof Bitstream)) { return false; } Class objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(other); @@ -419,11 +411,7 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport return false; } final Bitstream otherBitstream = (Bitstream) other; - if (!this.getID().equals(otherBitstream.getID())) { - return false; - } - - return true; + return this.getID().equals(otherBitstream.getID()); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java b/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java index d543cc7c6e..5cf787ffd5 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java @@ -40,7 +40,6 @@ import org.hibernate.proxy.HibernateProxyHelper; * when update is called. * * @author Robert Tansley - * @version $Revision$ */ @Entity @Table(name = "bitstreamformatregistry") @@ -120,6 +119,7 @@ public class BitstreamFormat implements Serializable, ReloadableEntity * * @return the internal identifier */ + @Override public final Integer getID() { return id; } @@ -267,7 +267,7 @@ public class BitstreamFormat implements Serializable, ReloadableEntity */ @Override public boolean equals(Object other) { - if (other == null) { + if (!(other instanceof BitstreamFormat)) { return false; } Class objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(other); @@ -275,11 +275,7 @@ public class BitstreamFormat implements Serializable, ReloadableEntity return false; } final BitstreamFormat otherBitstreamFormat = (BitstreamFormat) other; - if (!this.getID().equals(otherBitstreamFormat.getID())) { - return false; - } - - return true; + return this.getID().equals(otherBitstreamFormat.getID()); } @Override 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 10bf11c0bc..d180d80ab9 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -310,7 +310,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i * whitespace. */ if (value == null) { - clearMetadata(context, collection, field.SCHEMA, field.ELEMENT, field.QUALIFIER, Item.ANY); + clearMetadata(context, collection, field.schema, field.element, field.qualifier, Item.ANY); collection.setMetadataModified(); } else { super.setMetadataSingleValue(context, collection, field, null, value); diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index 810caaf4fd..088984928f 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -24,7 +24,6 @@ import javax.persistence.Table; import javax.persistence.Transient; import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.logging.log4j.Logger; import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CommunityService; @@ -42,18 +41,12 @@ import org.hibernate.proxy.HibernateProxyHelper; * update is called. * * @author Robert Tansley - * @version $Revision$ */ @Entity @Table(name = "community") @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") public class Community extends DSpaceObject implements DSpaceObjectLegacySupport { - /** - * log4j category - */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(Community.class); - @Column(name = "community_id", insertable = false, updatable = false) private Integer legacyId; @@ -215,7 +208,7 @@ public class Community extends DSpaceObject implements DSpaceObjectLegacySupport */ @Override public boolean equals(Object other) { - if (other == null) { + if (!(other instanceof Community)) { return false; } Class objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(other); @@ -223,11 +216,7 @@ public class Community extends DSpaceObject implements DSpaceObjectLegacySupport return false; } final Community otherCommunity = (Community) other; - if (!this.getID().equals(otherCommunity.getID())) { - return false; - } - - return true; + return this.getID().equals(otherCommunity.getID()); } @Override 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 4b953820ad..7a497d0e10 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -192,7 +192,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp * whitespace. */ if (value == null) { - clearMetadata(context, community, field.SCHEMA, field.ELEMENT, field.QUALIFIER, Item.ANY); + clearMetadata(context, community, field.schema, field.element, field.qualifier, Item.ANY); community.setMetadataModified(); } else { super.setMetadataSingleValue(context, community, field, null, value); diff --git a/dspace-api/src/main/java/org/dspace/content/DCDate.java b/dspace-api/src/main/java/org/dspace/content/DCDate.java index 4acccb2d84..47ed805d4a 100644 --- a/dspace-api/src/main/java/org/dspace/content/DCDate.java +++ b/dspace-api/src/main/java/org/dspace/content/DCDate.java @@ -39,7 +39,6 @@ import org.apache.logging.log4j.Logger; * * @author Robert Tansley * @author Larry Stone - * @version $Revision$ */ public class DCDate { /** @@ -370,6 +369,7 @@ public class DCDate { * * @return The date as a string. */ + @Override public String toString() { if (calendar == null) { return "null"; diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java index a486fed82a..73ca37f7c1 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java @@ -61,7 +61,7 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity handles = new ArrayList<>(); @OneToMany(fetch = FetchType.LAZY, mappedBy = "dSpaceObject", cascade = CascadeType.ALL) - private List resourcePolicies = new ArrayList<>(); + private final List resourcePolicies = new ArrayList<>(); /** * True if anything else was changed since last update() @@ -122,6 +122,7 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity implements @Override public String getMetadataFirstValue(T dso, MetadataFieldName field, String language) { List metadataValues - = getMetadata(dso, field.SCHEMA, field.ELEMENT, field.QUALIFIER, language); + = getMetadata(dso, field.schema, field.element, field.qualifier, language); if (CollectionUtils.isNotEmpty(metadataValues)) { return metadataValues.get(0).getValue(); } @@ -447,11 +447,11 @@ public abstract class DSpaceObjectServiceImpl implements String language, String value) throws SQLException { if (value != null) { - clearMetadata(context, dso, field.SCHEMA, field.ELEMENT, field.QUALIFIER, + clearMetadata(context, dso, field.schema, field.element, field.qualifier, language); String newValueLanguage = (Item.ANY.equals(language)) ? null : language; - addMetadata(context, dso, field.SCHEMA, field.ELEMENT, field.QUALIFIER, + addMetadata(context, dso, field.schema, field.element, field.qualifier, newValueLanguage, value); dso.setMetadataModified(); } diff --git a/dspace-api/src/main/java/org/dspace/content/InProgressSubmission.java b/dspace-api/src/main/java/org/dspace/content/InProgressSubmission.java index 5e7a04c4c9..42ef449c7d 100644 --- a/dspace-api/src/main/java/org/dspace/content/InProgressSubmission.java +++ b/dspace-api/src/main/java/org/dspace/content/InProgressSubmission.java @@ -17,7 +17,6 @@ import org.dspace.eperson.EPerson; * which stage of submission they are (in workspace or workflow system) * * @author Robert Tansley - * @version $Revision$ */ public interface InProgressSubmission extends ReloadableEntity { /** @@ -25,6 +24,7 @@ public interface InProgressSubmission extends ReloadableEntity { * * @return the internal identifier */ + @Override Integer getID(); /** diff --git a/dspace-api/src/main/java/org/dspace/content/Item.java b/dspace-api/src/main/java/org/dspace/content/Item.java index 22a9a4de52..b4b4ac2b2d 100644 --- a/dspace-api/src/main/java/org/dspace/content/Item.java +++ b/dspace-api/src/main/java/org/dspace/content/Item.java @@ -27,7 +27,6 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; -import org.apache.log4j.Logger; import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -48,17 +47,10 @@ import org.hibernate.proxy.HibernateProxyHelper; * * @author Robert Tansley * @author Martin Hald - * @version $Revision$ */ @Entity @Table(name = "item") public class Item extends DSpaceObject implements DSpaceObjectLegacySupport { - - /** - * log4j logger - */ - private static Logger log = Logger.getLogger(Item.class); - /** * Wild card for Dublin Core metadata qualifiers/languages */ @@ -286,7 +278,7 @@ public class Item extends DSpaceObject implements DSpaceObjectLegacySupport { * @return the bundles in an unordered array */ public List getBundles(String name) { - List matchingBundles = new ArrayList(); + List matchingBundles = new ArrayList<>(); // now only keep bundles with matching names List bunds = getBundles(); for (Bundle bundle : bunds) { @@ -317,7 +309,7 @@ public class Item extends DSpaceObject implements DSpaceObjectLegacySupport { /** * Return true if other is the same Item as - * this object, false otherwise + * this object, false otherwise. * * @param obj object to compare to * @return true if object passed in represents the same item @@ -325,7 +317,7 @@ public class Item extends DSpaceObject implements DSpaceObjectLegacySupport { */ @Override public boolean equals(Object obj) { - if (obj == null) { + if (!(obj instanceof Item)) { return false; } Class objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj); @@ -333,10 +325,7 @@ public class Item extends DSpaceObject implements DSpaceObjectLegacySupport { return false; } final Item otherItem = (Item) obj; - if (!this.getID().equals(otherItem.getID())) { - return false; - } - return true; + return this.getID().equals(otherItem.getID()); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataField.java b/dspace-api/src/main/java/org/dspace/content/MetadataField.java index 0ea176c751..8b76701199 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataField.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataField.java @@ -32,7 +32,6 @@ import org.hibernate.proxy.HibernateProxyHelper; * metadata element belongs in a field. * * @author Martin Hald - * @version $Revision$ * @see org.dspace.content.MetadataValue * @see org.dspace.content.MetadataSchema */ @@ -77,6 +76,7 @@ public class MetadataField implements ReloadableEntity { * * @return metadata field id */ + @Override public Integer getID() { return id; } @@ -164,7 +164,7 @@ public class MetadataField implements ReloadableEntity { */ @Override public boolean equals(Object obj) { - if (obj == null) { + if (!(obj instanceof MetadataField)) { return false; } Class objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj); @@ -175,10 +175,7 @@ public class MetadataField implements ReloadableEntity { if (!this.getID().equals(other.getID())) { return false; } - if (!getMetadataSchema().equals(other.getMetadataSchema())) { - return false; - } - return true; + return getMetadataSchema().equals(other.getMetadataSchema()); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataFieldName.java b/dspace-api/src/main/java/org/dspace/content/MetadataFieldName.java index 8c3dfc1bcc..8d7f4b0277 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataFieldName.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataFieldName.java @@ -17,13 +17,13 @@ import javax.annotation.Nonnull; */ public class MetadataFieldName { /** Name of the metadata schema which defines this field. Never null. */ - public final String SCHEMA; + public final String schema; /** Element name of this field. Never null. */ - public final String ELEMENT; + public final String element; /** Qualifier name of this field. May be {@code null}. */ - public final String QUALIFIER; + public final String qualifier; /** * Initialize a tuple of (schema, element, qualifier) to name a metadata field. @@ -40,9 +40,9 @@ public class MetadataFieldName { throw new NullPointerException("Element must not be null."); } - SCHEMA = schema; - ELEMENT = element; - QUALIFIER = qualifier; + this.schema = schema; + this.element = element; + this.qualifier = qualifier; } /** @@ -59,9 +59,9 @@ public class MetadataFieldName { throw new NullPointerException("Element must not be null."); } - SCHEMA = schema; - ELEMENT = element; - QUALIFIER = null; + this.schema = schema; + this.element = element; + qualifier = null; } /** @@ -79,9 +79,9 @@ public class MetadataFieldName { throw new IllegalArgumentException("Element must not be null."); } - SCHEMA = schema.getName(); - ELEMENT = element; - QUALIFIER = qualifier; + this.schema = schema.getName(); + this.element = element; + this.qualifier = qualifier; } /** @@ -98,9 +98,9 @@ public class MetadataFieldName { throw new IllegalArgumentException("Element must not be null."); } - SCHEMA = schema.getName(); - ELEMENT = element; - QUALIFIER = null; + this.schema = schema.getName(); + this.element = element; + qualifier = null; } /** @@ -110,9 +110,9 @@ public class MetadataFieldName { */ public MetadataFieldName(@Nonnull String name) { String[] elements = parse(name); - SCHEMA = elements[0]; - ELEMENT = elements[1]; - QUALIFIER = elements[2]; + schema = elements[0]; + element = elements[1]; + qualifier = elements[2]; } /** @@ -138,17 +138,17 @@ public class MetadataFieldName { /** * Format a dotted-atoms representation of this field name. - * @return SCHEMA.ELEMENT.QUALIFIER + * @return schema.element.qualifier */ @Override public String toString() { StringBuilder buffer = new StringBuilder(32); - buffer.append(SCHEMA) + buffer.append(schema) .append('.') - .append(ELEMENT); - if (null != QUALIFIER) { + .append(element); + if (null != qualifier) { buffer.append('.') - .append(QUALIFIER); + .append(qualifier); } return buffer.toString(); } diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java b/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java index 727181ee9d..f60e5e1604 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java @@ -30,7 +30,6 @@ import org.hibernate.proxy.HibernateProxyHelper; *

* * @author Martin Hald - * @version $Revision$ * @see org.dspace.content.MetadataValue * @see org.dspace.content.MetadataField */ @@ -129,6 +128,7 @@ public class MetadataSchema implements ReloadableEntity { * * @return schema record key */ + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java index 2d9808ae45..d1b636cdff 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java @@ -46,7 +46,7 @@ public class MetadataValue implements ReloadableEntity { @Column(name = "metadata_value_id") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "metadatavalue_seq") @SequenceGenerator(name = "metadatavalue_seq", sequenceName = "metadatavalue_seq", allocationSize = 1) - private Integer id; + private final Integer id; /** * The primary key for the metadata value @@ -104,6 +104,7 @@ public class MetadataValue implements ReloadableEntity { * * @return metadata value ID */ + @Override public Integer getID() { return id; } @@ -249,10 +250,7 @@ public class MetadataValue implements ReloadableEntity { if (!this.getID().equals(other.getID())) { return false; } - if (!this.getDSpaceObject().getID().equals(other.getDSpaceObject().getID())) { - return false; - } - return true; + return this.getDSpaceObject().getID().equals(other.getDSpaceObject().getID()); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java index b7fe2dfa16..764f195d1e 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java @@ -33,6 +33,7 @@ import org.dspace.core.Context; */ public interface BitstreamService extends DSpaceObjectService, DSpaceObjectLegacySupportService { + @Override public Bitstream find(Context context, UUID id) throws SQLException; public List findAll(Context context) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index e878367ec4..2181626250 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -46,8 +46,6 @@ import org.springframework.util.CollectionUtils; * changes and free up the resources. *

* The context object is also used as a cache for CM API objects. - * - * @version $Revision$ */ public class Context implements AutoCloseable { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(Context.class); diff --git a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java index cd0609e29f..68db217f1e 100644 --- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java +++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java @@ -217,12 +217,11 @@ public class I18nUtil { */ public static String getInputFormsFileName(Locale locale) { /** Name of the form definition XML file */ - String fileName = ""; final String FORM_DEF_FILE = "submission-forms"; final String FILE_TYPE = ".xml"; String defsFilename = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir") + File.separator + "config" + File.separator + FORM_DEF_FILE; - fileName = getFilename(locale, defsFilename, FILE_TYPE); + String fileName = getFilename(locale, defsFilename, FILE_TYPE); return fileName; } @@ -286,14 +285,13 @@ public class I18nUtil { */ public static String getDefaultLicense(Context context) { Locale locale = context.getCurrentLocale(); - String fileName = ""; /** Name of the default license */ final String DEF_LIC_FILE = "default"; final String FILE_TYPE = ".license"; String defsFilename = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir") + File.separator + "config" + File.separator + DEF_LIC_FILE; - fileName = getFilename(locale, defsFilename, FILE_TYPE); + String fileName = getFilename(locale, defsFilename, FILE_TYPE); return fileName; } @@ -316,8 +314,7 @@ public class I18nUtil { // with Language, Country String fileNameLC = null; // with Language - String fileNameL = null; - fileNameL = fileName + "_" + locale.getLanguage(); + String fileNameL = fileName + "_" + locale.getLanguage(); if (fileType == null) { fileType = ""; @@ -372,12 +369,11 @@ public class I18nUtil { * String - localized filename of an email template */ public static String getEmailFilename(Locale locale, String name) { - String templateName = ""; String templateFile = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir") + File.separator + "config" + File.separator + "emails" + File.separator + name; - templateName = getFilename(locale, templateFile, ""); + String templateName = getFilename(locale, templateFile, ""); return templateName; } @@ -389,7 +385,7 @@ public class I18nUtil { * @return array of locale results, possibly empty */ public static Locale[] parseLocales(String[] locales) { - List resultList = new ArrayList(); + List resultList = new ArrayList<>(); for (String ls : locales) { Locale lc = makeLocale(ls); if (lc != null) { diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index 18daa5a69e..8968c8950f 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -33,8 +33,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import com.coverity.security.Escape; +import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringSubstitutor; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -43,13 +45,12 @@ import org.dspace.services.factory.DSpaceServicesFactory; * Utility functions for DSpace. * * @author Peter Breton - * @version $Revision$ */ public final class Utils { /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(Utils.class); + private static final Logger log = LogManager.getLogger(Utils.class); private static final Pattern DURATION_PATTERN = Pattern .compile("(\\d+)([smhdwy])"); @@ -68,12 +69,12 @@ public final class Utils { private static int counter = 0; - private static Random random = new Random(); + private static final Random random = new Random(); - private static VMID vmid = new VMID(); + private static final VMID vmid = new VMID(); // for parseISO8601Date - private static SimpleDateFormat parseFmt[] = { + private static final SimpleDateFormat parseFmt[] = { // first try at parsing, has milliseconds (note General time zone) new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSz"), @@ -88,12 +89,14 @@ public final class Utils { // for formatISO8601Date // output canonical format (note RFC22 time zone, easier to hack) - private static SimpleDateFormat outFmtSecond = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"); + private static final SimpleDateFormat outFmtSecond + = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"); // output format with millsecond precision - private static SimpleDateFormat outFmtMillisec = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSZ"); + private static final SimpleDateFormat outFmtMillisec + = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSZ"); - private static Calendar outCal = GregorianCalendar.getInstance(); + private static final Calendar outCal = GregorianCalendar.getInstance(); /** * Private constructor @@ -107,7 +110,7 @@ public final class Utils { * @return MD5 checksum for the data in hex format. */ public static String getMD5(String data) { - return getMD5(data.getBytes()); + return getMD5(data.getBytes(StandardCharsets.UTF_8)); } /** @@ -150,7 +153,7 @@ public final class Utils { return null; } - StringBuffer result = new StringBuffer(); + StringBuilder result = new StringBuilder(); // This is far from the most efficient way to do things... for (int i = 0; i < data.length; i++) { @@ -194,10 +197,14 @@ public final class Utils { random.nextBytes(junk); - String input = new StringBuffer().append(vmid).append( - new java.util.Date()).append(Arrays.toString(junk)).append(counter++).toString(); + String input = new StringBuilder() + .append(vmid) + .append(new java.util.Date()) + .append(Arrays.toString(junk)) + .append(counter++) + .toString(); - return getMD5Bytes(input.getBytes()); + return getMD5Bytes(input.getBytes(StandardCharsets.UTF_8)); } // The following two methods are taken from the Jakarta IOUtil class. diff --git a/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java b/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java index d5ec37d60b..fa16d27369 100644 --- a/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java +++ b/dspace-api/src/main/java/org/dspace/curate/AbstractCurationTask.java @@ -13,7 +13,6 @@ import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; @@ -39,8 +38,6 @@ public abstract class AbstractCurationTask implements CurationTask { protected Curator curator = null; // curator-assigned taskId protected String taskId = null; - // logger - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(AbstractCurationTask.class); protected CommunityService communityService; protected ItemService itemService; protected HandleService handleService; diff --git a/dspace-api/src/main/java/org/dspace/curate/CitationPage.java b/dspace-api/src/main/java/org/dspace/curate/CitationPage.java index 386bf0ba92..0f02d5b757 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CitationPage.java +++ b/dspace-api/src/main/java/org/dspace/curate/CitationPage.java @@ -19,7 +19,6 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; -import org.dspace.content.BitstreamFormat; import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -46,7 +45,7 @@ public class CitationPage extends AbstractCurationTask { /** * Class Logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(CitationPage.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CitationPage.class); protected int status = Curator.CURATE_UNSET; protected String result = null; @@ -97,7 +96,7 @@ public class CitationPage extends AbstractCurationTask { //Determine if the DISPLAY bundle exits. If not, create it. List dBundles = itemService.getBundles(item, CitationPage.DISPLAY_BUNDLE_NAME); Bundle dBundle = null; - if (dBundles == null || dBundles.size() == 0) { + if (dBundles == null || dBundles.isEmpty()) { try { dBundle = bundleService.create(Curator.curationContext(), item, CitationPage.DISPLAY_BUNDLE_NAME); } catch (AuthorizeException e) { @@ -110,7 +109,7 @@ public class CitationPage extends AbstractCurationTask { //Create a map of the bitstreams in the displayBundle. This is used to //check if the bundle being cited is already in the display bundle. - Map displayMap = new HashMap(); + Map displayMap = new HashMap<>(); for (Bitstream bs : dBundle.getBitstreams()) { displayMap.put(bs.getName(), bs); } @@ -143,15 +142,16 @@ public class CitationPage extends AbstractCurationTask { // Loop through each file and generate a cover page for documents // that are PDFs. for (Bitstream bitstream : bitstreams) { - BitstreamFormat format = bitstream.getFormat(Curator.curationContext()); //If bitstream is a PDF document then it is citable. CitationDocumentService citationDocument = DisseminateServiceFactory.getInstance() .getCitationDocumentService(); if (citationDocument.canGenerateCitationVersion(Curator.curationContext(), bitstream)) { - this.resBuilder.append(item.getHandle() + " - " - + bitstream.getName() + " is citable."); + this.resBuilder.append(item.getHandle()) + .append(" - ") + .append(bitstream.getName()) + .append(" is citable."); try { //Create the cited document Pair citedDocument = @@ -168,7 +168,9 @@ public class CitationPage extends AbstractCurationTask { StringBuilder stack = new StringBuilder(); int numLines = Math.min(stackTrace.length, 12); for (int j = 0; j < numLines; j++) { - stack.append("\t" + stackTrace[j].toString() + "\n"); + stack.append("\t") + .append(stackTrace[j].toString()) + .append("\n"); } if (stackTrace.length > numLines) { stack.append("\t. . .\n"); @@ -180,8 +182,10 @@ public class CitationPage extends AbstractCurationTask { } } else { //bitstream is not a document - this.resBuilder.append(item.getHandle() + " - " - + bitstream.getName() + " is not citable.\n"); + this.resBuilder.append(item.getHandle()) + .append(" - ") + .append(bitstream.getName()) + .append(" is not citable.\n"); this.status = Curator.CURATE_SUCCESS; } } @@ -211,11 +215,11 @@ public class CitationPage extends AbstractCurationTask { //If we are modifying a file that is not in the //preservation bundle then we have to move it there. Context context = Curator.curationContext(); - if (bundle.getID() != pBundle.getID()) { + if (!bundle.getID().equals(pBundle.getID())) { bundleService.addBitstream(context, pBundle, bitstream); bundleService.removeBitstream(context, bundle, bitstream); List bitstreams = bundle.getBitstreams(); - if (bitstreams == null || bitstreams.size() == 0) { + if (bitstreams == null || bitstreams.isEmpty()) { itemService.removeBundle(context, item, bundle); } } @@ -235,9 +239,11 @@ public class CitationPage extends AbstractCurationTask { bitstreamService.setFormat(context, citedBitstream, bitstream.getFormat(Curator.curationContext())); citedBitstream.setDescription(context, bitstream.getDescription()); - this.resBuilder.append(" Added " - + citedBitstream.getName() - + " to the " + CitationPage.DISPLAY_BUNDLE_NAME + " bundle.\n"); + this.resBuilder.append(" Added ") + .append(citedBitstream.getName()) + .append(" to the ") + .append(CitationPage.DISPLAY_BUNDLE_NAME) + .append(" bundle.\n"); //Run update to propagate changes to the //database. diff --git a/dspace-api/src/main/java/org/dspace/curate/TaskResolver.java b/dspace-api/src/main/java/org/dspace/curate/TaskResolver.java index a9a8e8906a..2b12745d8f 100644 --- a/dspace-api/src/main/java/org/dspace/curate/TaskResolver.java +++ b/dspace-api/src/main/java/org/dspace/curate/TaskResolver.java @@ -15,11 +15,13 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.util.Properties; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.services.factory.DSpaceServicesFactory; @@ -64,7 +66,7 @@ import org.dspace.services.factory.DSpaceServicesFactory; public class TaskResolver { // logging service - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(TaskResolver.class); + private static final Logger log = LogManager.getLogger(TaskResolver.class); // base directory of task scripts & catalog name protected static final String CATALOG = "task.catalog"; @@ -94,7 +96,7 @@ public class TaskResolver { if (script.exists()) { BufferedReader reader = null; try { - reader = new BufferedReader(new FileReader(script)); + reader = new BufferedReader(new FileReader(script, StandardCharsets.UTF_8)); String line = null; while ((line = reader.readLine()) != null) { if (line.startsWith("#") && line.indexOf("$td=") > 0) { @@ -136,7 +138,7 @@ public class TaskResolver { catalog.put(taskName, descriptor); Writer writer = null; try { - writer = new FileWriter(new File(scriptDir, CATALOG)); + writer = new FileWriter(new File(scriptDir, CATALOG), StandardCharsets.UTF_8); catalog.store(writer, "do not edit"); } catch (IOException ioE) { log.error("Error saving scripted task catalog: " + CATALOG); @@ -179,7 +181,7 @@ public class TaskResolver { File script = new File(scriptDir, tokens[1]); if (script.exists()) { try { - Reader reader = new FileReader(script); + Reader reader = new FileReader(script, StandardCharsets.UTF_8); engine.eval(reader); reader.close(); // third token is the constructor expression for the class @@ -212,7 +214,7 @@ public class TaskResolver { File catalogFile = new File(scriptDir, CATALOG); if (catalogFile.exists()) { try { - Reader reader = new FileReader(catalogFile); + Reader reader = new FileReader(catalogFile, StandardCharsets.UTF_8); catalog.load(reader); reader.close(); } catch (IOException ioE) { diff --git a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java index 97187c79ed..b3ee42fca1 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java +++ b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java @@ -16,9 +16,9 @@ import java.io.Reader; import java.io.SequenceInputStream; import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import javax.annotation.Nullable; @@ -55,7 +55,7 @@ public class FullTextContentStreams extends ContentStreamBase { } protected void init(Item parentItem) { - fullTextStreams = new LinkedList<>(); + fullTextStreams = new ArrayList<>(); if (parentItem != null) { sourceInfo = parentItem.getHandle(); @@ -149,8 +149,8 @@ public class FullTextContentStreams extends ContentStreamBase { } private class FullTextBitstream { - private String itemHandle; - private Bitstream bitstream; + private final String itemHandle; + private final Bitstream bitstream; public FullTextBitstream(final String parentHandle, final Bitstream file) { this.itemHandle = parentHandle; @@ -179,18 +179,20 @@ public class FullTextContentStreams extends ContentStreamBase { } } - private class FullTextEnumeration implements Enumeration { + private static class FullTextEnumeration implements Enumeration { private final Iterator fulltextIterator; - public FullTextEnumeration(final Iterator fulltextStreams) { - this.fulltextIterator = fulltextStreams; + public FullTextEnumeration(final Iterator fulltextIterator) { + this.fulltextIterator = fulltextIterator; } + @Override public boolean hasMoreElements() { return fulltextIterator.hasNext(); } + @Override public InputStream nextElement() { InputStream inputStream = null; FullTextBitstream bitstream = null; diff --git a/dspace-api/src/main/java/org/dspace/event/ConsumerProfile.java b/dspace-api/src/main/java/org/dspace/event/ConsumerProfile.java index c66c331332..d1059f8e2f 100644 --- a/dspace-api/src/main/java/org/dspace/event/ConsumerProfile.java +++ b/dspace-api/src/main/java/org/dspace/event/ConsumerProfile.java @@ -104,7 +104,9 @@ public class ConsumerProfile { "No filters configured for consumer named: " + name); } - consumer = (Consumer) Class.forName(className.trim()).getDeclaredConstructor().newInstance(); + consumer = Class.forName(className.trim()) + .asSubclass(Consumer.class) + .getDeclaredConstructor().newInstance(); // Each "filter" is + : ... filters = new ArrayList<>(); diff --git a/dspace-api/src/main/java/org/dspace/event/Event.java b/dspace-api/src/main/java/org/dspace/event/Event.java index 45b6407b28..af8b2d4571 100644 --- a/dspace-api/src/main/java/org/dspace/event/Event.java +++ b/dspace-api/src/main/java/org/dspace/event/Event.java @@ -48,8 +48,6 @@ import org.dspace.event.factory.EventServiceFactory; * significance varies by the combination of action and subject type. *

  • - timestamp -- exact millisecond timestamp at which event was logged.
  • * - * - * @version $Revision$ */ public class Event implements Serializable { private static final long serialVersionUID = 1L; @@ -308,6 +306,7 @@ public class Event implements Serializable { * @param other the event to compare this one to * @return true if events are "equal", false otherwise. */ + @Override public boolean equals(Object other) { if (other instanceof Event) { Event otherEvent = (Event) other; @@ -315,14 +314,15 @@ public class Event implements Serializable { .equals(otherEvent.detail)) && this.eventType == otherEvent.eventType && this.subjectType == otherEvent.subjectType - && this.subjectID == otherEvent.subjectID + && this.subjectID.equals(otherEvent.subjectID) && this.objectType == otherEvent.objectType - && this.objectID == otherEvent.objectID; + && this.objectID.equals(otherEvent.objectID); } return false; } + @Override public int hashCode() { return new HashCodeBuilder().append(this.detail) .append(eventType) @@ -634,6 +634,7 @@ public class Event implements Serializable { * @return Detailed string representation of contents of this event, to * help in logging and debugging. */ + @Override public String toString() { return "org.dspace.event.Event(eventType=" + this.getEventTypeAsString() diff --git a/dspace-api/src/main/java/org/dspace/handle/Handle.java b/dspace-api/src/main/java/org/dspace/handle/Handle.java index 76fed105b9..c35511353a 100644 --- a/dspace-api/src/main/java/org/dspace/handle/Handle.java +++ b/dspace-api/src/main/java/org/dspace/handle/Handle.java @@ -105,7 +105,7 @@ public class Handle implements ReloadableEntity { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof Handle)) { return false; } diff --git a/dspace-api/src/main/java/org/dspace/text/filter/InitialArticleWord.java b/dspace-api/src/main/java/org/dspace/text/filter/InitialArticleWord.java index 700b25748e..167b201e0f 100644 --- a/dspace-api/src/main/java/org/dspace/text/filter/InitialArticleWord.java +++ b/dspace-api/src/main/java/org/dspace/text/filter/InitialArticleWord.java @@ -110,7 +110,7 @@ public abstract class InitialArticleWord implements TextFilter { return str.substring(cutPos); } else { // No - move the initial article word to the end - return new StringBuffer(str.substring(cutPos)) + return new StringBuilder(str.substring(cutPos)) .append(wordSeparator) .append(str.substring(initialStart, initialEnd)) .toString(); @@ -124,10 +124,12 @@ public abstract class InitialArticleWord implements TextFilter { } protected InitialArticleWord(boolean stripWord) { + this.wordSeparator = ", "; stripInitialArticle = stripWord; } protected InitialArticleWord() { + this.wordSeparator = ", "; stripInitialArticle = false; } @@ -138,9 +140,8 @@ public abstract class InitialArticleWord implements TextFilter { * @return An array of definite/indefinite article words */ protected abstract String[] getArticleWords(String lang); - // Separator to use when appending article to end - private String wordSeparator = ", "; + private final String wordSeparator; // Flag to signify initial article word should be removed // If false, then the initial article word is appended to the end diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/InProgressUser.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/InProgressUser.java index 5cd714345e..efbd26bde5 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/InProgressUser.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/InProgressUser.java @@ -59,6 +59,7 @@ public class InProgressUser implements ReloadableEntity { } + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataFieldNameTest.java b/dspace-api/src/test/java/org/dspace/content/MetadataFieldNameTest.java index f0ca7d25d8..a3a069b35e 100644 --- a/dspace-api/src/test/java/org/dspace/content/MetadataFieldNameTest.java +++ b/dspace-api/src/test/java/org/dspace/content/MetadataFieldNameTest.java @@ -26,17 +26,17 @@ public class MetadataFieldNameTest { @Test public void testConstruct3() { MetadataFieldName instance = new MetadataFieldName("one", "two", "three"); - assertEquals("Incorrect schema", "one", instance.SCHEMA); - assertEquals("Incorrect element", "two", instance.ELEMENT); - assertEquals("Incorrect qualifier", "three", instance.QUALIFIER); + assertEquals("Incorrect schema", "one", instance.schema); + assertEquals("Incorrect element", "two", instance.element); + assertEquals("Incorrect qualifier", "three", instance.qualifier); } @Test public void testConstruct2() { MetadataFieldName instance = new MetadataFieldName("one", "two"); - assertEquals("Incorrect schema", "one", instance.SCHEMA); - assertEquals("Incorrect element", "two", instance.ELEMENT); - assertNull("Incorrect qualifier", instance.QUALIFIER); + assertEquals("Incorrect schema", "one", instance.schema); + assertEquals("Incorrect element", "two", instance.element); + assertNull("Incorrect qualifier", instance.qualifier); } @Test(expected = NullPointerException.class) From f6112babc3685a2eb84ee55e50215835e43c260d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 24 Nov 2020 20:22:06 -0500 Subject: [PATCH 0010/1254] More errorprone warnings. #3061 --- .../dspace/administer/MetadataImporter.java | 17 +++----- .../org/dspace/app/bulkedit/DSpaceCSV.java | 42 ++++++++---------- .../dspace/app/itemupdate/DtoMetadata.java | 1 + .../dspace/app/itemupdate/ItemArchive.java | 5 ++- .../org/dspace/app/sherpa/SHERPAResponse.java | 9 ++-- .../app/statistics/CreateStatReport.java | 43 ++++++------------- .../main/java/org/dspace/app/util/WebApp.java | 1 + .../java/org/dspace/app/util/XMLUtils.java | 11 +++-- .../authenticate/PasswordAuthentication.java | 8 ++-- .../authorize/AuthorizeServiceImpl.java | 17 ++++---- .../dspace/checker/MostRecentChecksum.java | 2 +- .../org/dspace/checker/ResultsLogger.java | 2 +- .../org/dspace/content/BitstreamFormat.java | 4 +- .../main/java/org/dspace/content/Bundle.java | 10 ++--- .../main/java/org/dspace/content/DCDate.java | 29 ++++++------- .../org/dspace/content/DCSeriesNumber.java | 4 +- .../content/DSpaceObjectServiceImpl.java | 7 ++- .../java/org/dspace/content/EntityType.java | 7 ++- .../java/org/dspace/content/Relationship.java | 1 + .../org/dspace/content/RelationshipType.java | 9 ++-- .../dao/impl/MetadataSchemaDAOImpl.java | 4 +- .../content/packager/PackageException.java | 7 --- .../content/packager/PackageParameters.java | 2 +- .../org/dspace/content/virtual/UUIDValue.java | 5 +-- .../main/java/org/dspace/core/Context.java | 36 ++++++++-------- .../main/java/org/dspace/core/I18nUtil.java | 4 +- .../java/org/dspace/curate/FileTaskQueue.java | 17 +++++--- .../java/org/dspace/curate/ResolvedTask.java | 6 +-- .../org/dspace/discovery/DiscoverQuery.java | 37 ++++++++-------- .../discovery/FullTextContentStreams.java | 5 +++ .../discovery/SolrServiceFileInfoPlugin.java | 2 +- .../indexobject/AbstractIndexableObject.java | 13 +++--- .../java/org/dspace/eperson/Subscription.java | 1 + .../eperson/dao/impl/SubscriptionDAOImpl.java | 3 +- .../dspace/external/OrcidRestConnector.java | 8 ++-- .../external/model/ExternalDataObject.java | 8 ++-- .../provider/orcid/xml/Converter.java | 6 --- .../org/dspace/harvest/HarvestedItem.java | 2 +- .../main/java/org/dspace/identifier/DOI.java | 1 + .../identifier/dao/impl/DOIDAOImpl.java | 14 +++--- .../external/datamodel/ImportRecord.java | 6 +-- .../AbstractMetadataFieldMapping.java | 15 ++++--- .../metadatamapping/MetadataFieldConfig.java | 10 ++--- .../scripts/DSpaceCommandLineParameter.java | 12 +++--- .../org/dspace/scripts/DSpaceRunnable.java | 10 ++--- .../main/java/org/dspace/scripts/Process.java | 8 ++-- .../main/java/org/dspace/search/Harvest.java | 12 +++--- .../java/org/dspace/statistics/Dataset.java | 15 ++++--- .../SolrLoggerUsageEventListener.java | 7 +-- .../content/StatisticsDataVisits.java | 10 +---- .../lookup/ValueConcatenationModifier.java | 10 ++--- .../submit/util/ItemSubmissionLookupDTO.java | 8 ++-- .../java/org/dspace/versioning/Version.java | 1 + .../org/dspace/versioning/VersionHistory.java | 10 +---- .../dspace/workflow/WorkflowException.java | 3 +- .../workflowbasic/BasicWorkflowItem.java | 1 + .../dspace/workflowbasic/TaskListItem.java | 1 + .../org/dspace/xmlworkflow/RoleMembers.java | 10 ++--- .../WorkflowConfigurationException.java | 7 +-- .../storedcomponents/ClaimedTask.java | 13 +++--- .../storedcomponents/CollectionRole.java | 1 + .../storedcomponents/PoolTask.java | 13 +++--- .../storedcomponents/WorkflowItemRole.java | 6 +-- .../org/dspace/AbstractIntegrationTest.java | 4 +- .../dspace/app/bulkedit/MetadataImportIT.java | 14 +++--- .../app/csv/CSVMetadataImportReferenceIT.java | 27 ++++++------ .../util/AbstractBuilderCleanupUtil.java | 40 ++++++++--------- .../dspace/content/MetadataFieldNameTest.java | 10 +++-- .../DSpaceControlledVocabularyTest.java | 5 +-- .../MockCCLicenseConnectorServiceImpl.java | 6 +-- .../ITIrusExportUsageEventListener.java | 27 ++++++------ .../BitstreamEventProcessorTest.java | 6 +-- .../service/MockOpenUrlServiceImpl.java | 12 +++--- 73 files changed, 360 insertions(+), 390 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java index 37a89fa694..42461d7210 100644 --- a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java +++ b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java @@ -81,7 +81,7 @@ public class MetadataImporter { * @throws SQLException if database error * @throws IOException if IO error * @throws TransformerException if transformer error - * @throws ParserConfigurationException if config error + * @throws ParserConfigurationException if configuration error * @throws AuthorizeException if authorization error * @throws SAXException if parser error * @throws NonUniqueMetadataException if duplicate metadata @@ -91,7 +91,6 @@ public class MetadataImporter { throws ParseException, SQLException, IOException, TransformerException, ParserConfigurationException, AuthorizeException, SAXException, NonUniqueMetadataException, RegistryImportException { - boolean forceUpdate = false; // create an options object and populate it CommandLineParser parser = new DefaultParser(); @@ -100,16 +99,14 @@ public class MetadataImporter { options.addOption("u", "update", false, "update an existing schema"); CommandLine line = parser.parse(options, args); - String file = null; if (line.hasOption('f')) { - file = line.getOptionValue('f'); + String file = line.getOptionValue('f'); + boolean forceUpdate = line.hasOption('u'); + loadRegistry(file, forceUpdate); } else { usage(); - System.exit(0); + System.exit(1); } - - forceUpdate = line.hasOption('u'); - loadRegistry(file, forceUpdate); } /** @@ -120,7 +117,7 @@ public class MetadataImporter { * @throws SQLException if database error * @throws IOException if IO error * @throws TransformerException if transformer error - * @throws ParserConfigurationException if config error + * @throws ParserConfigurationException if configuration error * @throws AuthorizeException if authorization error * @throws SAXException if parser error * @throws NonUniqueMetadataException if duplicate metadata @@ -227,7 +224,7 @@ public class MetadataImporter { /** * Process a node in the metadata registry XML file. The node must * be a "dc-type" node. If the type already exists, then it - * will not be reimported + * will not be re-imported. * * @param context DSpace context object * @param node the node in the DOM tree diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index ad7824bebf..e6ae91ece3 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -138,7 +138,7 @@ public class DSpaceCSV implements Serializable { /** * Create a new instance, reading the lines in from file * - * @param inputStream the inputstream to read from + * @param inputStream the input stream to read from * @param c The DSpace Context * @throws Exception thrown if there is an error reading or processing the file */ @@ -159,7 +159,7 @@ public class DSpaceCSV implements Serializable { columnCounter++; // Remove surrounding quotes if there are any - if ((element.startsWith("\"")) && (element.endsWith("\""))) { + if (element.startsWith("\"") && element.endsWith("\"")) { element = element.substring(1, element.length() - 1); } @@ -334,15 +334,15 @@ public class DSpaceCSV implements Serializable { /** * Set the value separator for multiple values stored in one csv value. * - * Is set in bulkedit.cfg as valueseparator + * Is set in {@code bulkedit.cfg} as {@code valueseparator}. * - * If not set, defaults to double pipe '||' + * If not set, defaults to double pipe '||'. */ private void setValueSeparator() { // Get the value separator valueSeparator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("bulkedit.valueseparator"); - if ((valueSeparator != null) && (!"".equals(valueSeparator.trim()))) { + if ((valueSeparator != null) && !valueSeparator.trim().isEmpty()) { valueSeparator = valueSeparator.trim(); } else { valueSeparator = "||"; @@ -357,7 +357,7 @@ public class DSpaceCSV implements Serializable { /** * Set the field separator use to separate fields in the csv. * - * Is set in bulkedit.cfg as fieldseparator + * Is set in {@code bulkedit.cfg} as {@code fieldseparator}. * * If not set, defaults to comma ','. * @@ -368,7 +368,7 @@ public class DSpaceCSV implements Serializable { // Get the value separator fieldSeparator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("bulkedit.fieldseparator"); - if ((fieldSeparator != null) && (!"".equals(fieldSeparator.trim()))) { + if ((fieldSeparator != null) && !fieldSeparator.trim().isEmpty()) { fieldSeparator = fieldSeparator.trim(); if ("tab".equals(fieldSeparator)) { fieldSeparator = "\t"; @@ -392,15 +392,15 @@ public class DSpaceCSV implements Serializable { /** * Set the authority separator for value with authority data. * - * Is set in dspace.cfg as bulkedit.authorityseparator + * Is set in {@code dspace.cfg} as {@code bulkedit.authorityseparator}. * - * If not set, defaults to double colon '::' + * If not set, defaults to double colon '::'. */ private void setAuthoritySeparator() { // Get the value separator authoritySeparator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("bulkedit.authorityseparator"); - if ((authoritySeparator != null) && (!"".equals(authoritySeparator.trim()))) { + if ((authoritySeparator != null) && !authoritySeparator.trim().isEmpty()) { authoritySeparator = authoritySeparator.trim(); } else { authoritySeparator = "::"; @@ -505,7 +505,7 @@ public class DSpaceCSV implements Serializable { int i = 0; for (String part : bits) { int bitcounter = part.length() - part.replaceAll("\"", "").length(); - if ((part.startsWith("\"")) && ((!part.endsWith("\"")) || ((bitcounter & 1) == 1))) { + if (part.startsWith("\"") && (!part.endsWith("\"") || ((bitcounter & 1) == 1))) { found = true; String add = bits.get(i) + fieldSeparator + bits.get(i + 1); bits.remove(i); @@ -521,7 +521,7 @@ public class DSpaceCSV implements Serializable { // Deal with quotes around the elements int i = 0; for (String part : bits) { - if ((part.startsWith("\"")) && (part.endsWith("\""))) { + if (part.startsWith("\"") && part.endsWith("\"")) { part = part.substring(1, part.length() - 1); bits.set(i, part); } @@ -561,7 +561,7 @@ public class DSpaceCSV implements Serializable { for (String part : bits) { if (i > 0) { // Is this a last empty item? - if ((last) && (i == headings.size())) { + if (last && (i == headings.size())) { part = ""; } @@ -574,7 +574,7 @@ public class DSpaceCSV implements Serializable { csvLine.add(headings.get(i - 1), null); String[] elements = part.split(escapedValueSeparator); for (String element : elements) { - if ((element != null) && (!"".equals(element))) { + if ((element != null) && !"".equals(element)) { csvLine.add(headings.get(i - 1), element); } } @@ -626,18 +626,18 @@ public class DSpaceCSV implements Serializable { public InputStream getInputStream() { StringBuilder stringBuilder = new StringBuilder(); for (String csvLine : getCSVLinesAsStringArray()) { - stringBuilder.append(csvLine + "\n"); + stringBuilder.append(csvLine).append("\n"); } return IOUtils.toInputStream(stringBuilder.toString(), StandardCharsets.UTF_8); } /** - * Is it Ok to export this value? When exportAll is set to false, we don't export + * Is it okay to export this value? When exportAll is set to false, we don't export * some of the metadata elements. * - * The list can be configured via the key ignore-on-export in bulkedit.cfg + * The list can be configured via the key ignore-on-export in {@code bulkedit.cfg}. * - * @param md The Metadatum to examine + * @param md The MetadataField to examine * @return Whether or not it is OK to export this element */ protected boolean okToExport(MetadataField md) { @@ -646,12 +646,8 @@ public class DSpaceCSV implements Serializable { if (md.getQualifier() != null) { key += "." + md.getQualifier(); } - if (ignore.get(key) != null) { - return false; - } - // Must be OK, so don't ignore - return true; + return ignore.get(key) == null; } /** diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/DtoMetadata.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/DtoMetadata.java index 6e4a4a88d6..e67b2221e4 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/DtoMetadata.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/DtoMetadata.java @@ -120,6 +120,7 @@ class DtoMetadata { return true; } + @Override public String toString() { String s = "\tSchema: " + schema + " Element: " + element; if (qualifier != null) { diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java index 2270d736a8..26de45caf7 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; @@ -55,7 +56,7 @@ public class ItemArchive { protected Transformer transformer = null; protected List dtomList = null; - protected List undoDtomList = new ArrayList(); + protected List undoDtomList = new ArrayList<>(); protected List undoAddContents = new ArrayList<>(); // for undo of add @@ -325,7 +326,7 @@ public class ItemArchive { PrintWriter pw = null; try { File f = new File(dir, ItemUpdate.DELETE_CONTENTS_FILE); - pw = new PrintWriter(new BufferedWriter(new FileWriter(f))); + pw = new PrintWriter(new BufferedWriter(new FileWriter(f, StandardCharsets.UTF_8))); for (UUID i : undoAddContents) { pw.println(i); } diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java index bd2909c0c1..b0ca1e3d93 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAResponse.java @@ -8,12 +8,13 @@ package org.dspace.app.sherpa; import java.io.InputStream; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.util.XMLUtils; import org.w3c.dom.Document; @@ -25,7 +26,7 @@ import org.w3c.dom.Element; * @author Andrea Bollini */ public class SHERPAResponse { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SHERPAResponse.class); + private static final Logger log = LogManager.getLogger(SHERPAResponse.class); private int numHits; @@ -81,7 +82,7 @@ public class SHERPAResponse { publishersElement, "publisher"); if (journalsList != null) { - journals = new LinkedList(); + journals = new ArrayList<>(journalsList.size()); for (Element journalElement : journalsList) { journals.add(new SHERPAJournal( XMLUtils.getElementValue(journalElement, "jtitle"), @@ -92,7 +93,7 @@ public class SHERPAResponse { } if (publishersList != null) { - publishers = new LinkedList(); + publishers = new ArrayList<>(publishersList.size()); for (Element publisherElement : publishersList) { Element preprintsElement = XMLUtils.getSingleElement( publisherElement, "preprints"); diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/CreateStatReport.java b/dspace-api/src/main/java/org/dspace/app/statistics/CreateStatReport.java index 5785d1ee97..a7d5c4a66a 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/CreateStatReport.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/CreateStatReport.java @@ -64,10 +64,6 @@ public class CreateStatReport { */ private static Context context; - /** - * the config file from which to configure the analyser - */ - /** * Default constructor */ @@ -170,22 +166,19 @@ public class CreateStatReport { String myLogDir = null; String myFileTemplate = null; String myConfigFile = null; - StringBuffer myOutFile = null; - Date myStartDate = null; - Date myEndDate = null; boolean myLookUp = false; Calendar start = new GregorianCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.getActualMinimum(Calendar.DAY_OF_MONTH)); - myStartDate = start.getTime(); + Date myStartDate = start.getTime(); Calendar end = new GregorianCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); - myEndDate = end.getTime(); + Date myEndDate = end.getTime(); - myOutFile = new StringBuffer(outputLogDirectory); + StringBuilder myOutFile = new StringBuilder(outputLogDirectory); myOutFile.append(outputPrefix); myOutFile.append(calendar.get(Calendar.YEAR)); myOutFile.append("-"); @@ -211,12 +204,11 @@ public class CreateStatReport { String myLogDir = null; String myFileTemplate = null; String myConfigFile = null; - StringBuffer myOutFile = null; Date myStartDate = null; Date myEndDate = null; boolean myLookUp = false; - myOutFile = new StringBuffer(outputLogDirectory); + StringBuilder myOutFile = new StringBuilder(outputLogDirectory); myOutFile.append(outputPrefix); myOutFile.append(calendar.get(Calendar.YEAR)); myOutFile.append("-"); @@ -245,9 +237,6 @@ public class CreateStatReport { String myLogDir = null; String myFileTemplate = null; String myConfigFile = null; - StringBuffer myOutFile = null; - Date myStartDate = null; - Date myEndDate = null; boolean myLookUp = false; Calendar reportEndDate = new GregorianCalendar(calendar.get(Calendar.YEAR), @@ -260,14 +249,14 @@ public class CreateStatReport { Calendar start = new GregorianCalendar(currentMonth.get(Calendar.YEAR), currentMonth.get(Calendar.MONTH), currentMonth.getActualMinimum(Calendar.DAY_OF_MONTH)); - myStartDate = start.getTime(); + Date myStartDate = start.getTime(); Calendar end = new GregorianCalendar(currentMonth.get(Calendar.YEAR), currentMonth.get(Calendar.MONTH), currentMonth.getActualMaximum(Calendar.DAY_OF_MONTH)); - myEndDate = end.getTime(); + Date myEndDate = end.getTime(); - myOutFile = new StringBuffer(outputLogDirectory); + StringBuilder myOutFile = new StringBuilder(outputLogDirectory); myOutFile.append(outputPrefix); myOutFile.append(currentMonth.get(Calendar.YEAR)); myOutFile.append("-"); @@ -293,11 +282,9 @@ public class CreateStatReport { String outputPrefix = "report-general-"; String myFormat = "html"; - StringBuffer myInput = null; - StringBuffer myOutput = null; String myMap = null; - myInput = new StringBuffer(outputLogDirectory); + StringBuilder myInput = new StringBuilder(outputLogDirectory); myInput.append(inputPrefix); myInput.append(calendar.get(Calendar.YEAR)); myInput.append("-"); @@ -306,7 +293,7 @@ public class CreateStatReport { myInput.append(calendar.get(Calendar.DAY_OF_MONTH)); myInput.append(outputSuffix); - myOutput = new StringBuffer(outputReportDirectory); + StringBuilder myOutput = new StringBuilder(outputReportDirectory); myOutput.append(outputPrefix); myOutput.append(calendar.get(Calendar.YEAR)); myOutput.append("-"); @@ -332,8 +319,6 @@ public class CreateStatReport { String outputPrefix = "report-"; String myFormat = "html"; - StringBuffer myInput = null; - StringBuffer myOutput = null; String myMap = null; Calendar reportEndDate = new GregorianCalendar(calendar.get(Calendar.YEAR), @@ -344,14 +329,14 @@ public class CreateStatReport { while (currentMonth.before(reportEndDate)) { - myInput = new StringBuffer(outputLogDirectory); + StringBuilder myInput = new StringBuilder(outputLogDirectory); myInput.append(inputPrefix); myInput.append(currentMonth.get(Calendar.YEAR)); myInput.append("-"); myInput.append(currentMonth.get(Calendar.MONTH) + 1); myInput.append(outputSuffix); - myOutput = new StringBuffer(outputReportDirectory); + StringBuilder myOutput = new StringBuilder(outputReportDirectory); myOutput.append(outputPrefix); myOutput.append(currentMonth.get(Calendar.YEAR)); myOutput.append("-"); @@ -376,18 +361,16 @@ public class CreateStatReport { String outputPrefix = "report-"; String myFormat = "html"; - StringBuffer myInput = null; - StringBuffer myOutput = null; String myMap = null; - myInput = new StringBuffer(outputLogDirectory); + StringBuilder myInput = new StringBuilder(outputLogDirectory); myInput.append(inputPrefix); myInput.append(calendar.get(Calendar.YEAR)); myInput.append("-"); myInput.append(calendar.get(Calendar.MONTH) + 1); myInput.append(outputSuffix); - myOutput = new StringBuffer(outputReportDirectory); + StringBuilder myOutput = new StringBuilder(outputReportDirectory); myOutput.append(outputPrefix); myOutput.append(calendar.get(Calendar.YEAR)); myOutput.append("-"); diff --git a/dspace-api/src/main/java/org/dspace/app/util/WebApp.java b/dspace-api/src/main/java/org/dspace/app/util/WebApp.java index 7cd2bd8fea..2f42c1459f 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/WebApp.java +++ b/dspace-api/src/main/java/org/dspace/app/util/WebApp.java @@ -58,6 +58,7 @@ public class WebApp implements ReloadableEntity { } + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java b/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java index 884b2a6a92..c39d0d26fd 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/XMLUtils.java @@ -8,7 +8,6 @@ package org.dspace.app.util; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -29,13 +28,13 @@ public class XMLUtils { /** * @param dataRoot the starting node - * @param name the name of the subelement to find + * @param name the tag name of the child element to find. * @return the list of all DOM Element with the provided name direct child * of the starting node */ public static List getElementList(Element dataRoot, String name) { NodeList list = dataRoot.getElementsByTagName(name); - List listElements = new ArrayList(); + List listElements = new ArrayList<>(); for (int i = 0; i < list.getLength(); i++) { Element item = (Element) list.item(i); if (item.getParentNode().equals(dataRoot)) { @@ -105,7 +104,7 @@ public class XMLUtils { /** * @param rootElement the starting node - * @param subElementName the name of the subelement to find + * @param subElementName the tag name of the child element to find. * @return a list of string including all the text contents of the sub * element with the specified name. If there are not sub element * with the supplied name the method will return null @@ -121,7 +120,7 @@ public class XMLUtils { return null; } - List result = new LinkedList(); + List result = new ArrayList<>(); for (Element el : subElements) { if (StringUtils.isNotBlank(el.getTextContent())) { result.add(el.getTextContent().trim()); @@ -152,7 +151,7 @@ public class XMLUtils { return null; } - List result = new LinkedList(); + List result = new ArrayList<>(); for (Element el : subElements) { String[] tmp = new String[fieldsName.length]; for (int idx = 0; idx < fieldsName.length; idx++) { diff --git a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java index 86cfb50c5f..045e0421ae 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java @@ -41,7 +41,6 @@ import org.dspace.services.factory.DSpaceServicesFactory; * Basic Auth username and password to the AuthenticationManager. * * @author Larry Stone - * @version $Revision$ */ public class PasswordAuthentication implements AuthenticationMethod { @@ -49,7 +48,7 @@ public class PasswordAuthentication /** * log4j category */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(PasswordAuthentication.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(PasswordAuthentication.class); /** @@ -142,7 +141,7 @@ public class PasswordAuthentication .toString())) { String groupName = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("authentication-password.login.specialgroup"); - if ((groupName != null) && (!groupName.trim().equals(""))) { + if ((groupName != null) && !groupName.trim().isEmpty()) { Group specialGroup = EPersonServiceFactory.getInstance().getGroupService() .findByName(context, groupName); if (specialGroup == null) { @@ -195,9 +194,8 @@ public class PasswordAuthentication HttpServletRequest request) throws SQLException { if (username != null && password != null) { - EPerson eperson = null; log.info(LogManager.getHeader(context, "authenticate", "attempting password auth of user=" + username)); - eperson = EPersonServiceFactory.getInstance().getEPersonService() + EPerson eperson = EPersonServiceFactory.getInstance().getEPersonService() .findByEmail(context, username.toLowerCase()); if (eperson == null) { 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 eb7d60d84c..3837ceb7bd 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -228,7 +227,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { // If authorization was given before and cached Boolean cachedResult = c.getCachedAuthorizationResult(o, action, e); if (cachedResult != null) { - return cachedResult.booleanValue(); + return cachedResult; } // is eperson set? if not, userToCheck = null (anonymous) @@ -293,7 +292,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { } if ((rp.getGroup() != null) - && (groupService.isMember(c, e, rp.getGroup()))) { + && groupService.isMember(c, e, rp.getGroup())) { // group was set, and eperson is a member // of that group c.cacheAuthorizedAction(o, action, e, true, rp); @@ -351,7 +350,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { Boolean cachedResult = c.getCachedAuthorizationResult(o, Constants.ADMIN, e); if (cachedResult != null) { - return cachedResult.booleanValue(); + return cachedResult; } // @@ -368,7 +367,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { } if ((rp.getGroup() != null) - && (groupService.isMember(c, e, rp.getGroup()))) { + && groupService.isMember(c, e, rp.getGroup())) { // group was set, and eperson is a member // of that group c.cacheAuthorizedAction(o, Constants.ADMIN, e, true, rp); @@ -428,6 +427,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { } } + @Override public boolean isCommunityAdmin(Context c) throws SQLException { EPerson e = c.getCurrentUser(); return isCommunityAdmin(c, e); @@ -448,6 +448,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { return false; } + @Override public boolean isCollectionAdmin(Context c) throws SQLException { EPerson e = c.getCurrentUser(); return isCollectionAdmin(c, e); @@ -527,7 +528,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { List policies = getPolicies(c, src); //Only inherit non-ADMIN policies (since ADMIN policies are automatically inherited) - List nonAdminPolicies = new ArrayList(); + List nonAdminPolicies = new ArrayList<>(); for (ResourcePolicy rp : policies) { if (rp.getAction() != Constants.ADMIN) { nonAdminPolicies.add(rp); @@ -550,7 +551,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { public void addPolicies(Context c, List policies, DSpaceObject dest) throws SQLException, AuthorizeException { // now add them to the destination object - List newPolicies = new LinkedList<>(); + List newPolicies = new ArrayList<>(policies.size()); for (ResourcePolicy srp : policies) { ResourcePolicy rp = resourcePolicyService.create(c); @@ -625,7 +626,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { int actionID) throws java.sql.SQLException { List policies = getPoliciesActionFilter(c, o, actionID); - List groups = new ArrayList(); + List groups = new ArrayList<>(); for (ResourcePolicy resourcePolicy : policies) { if (resourcePolicy.getGroup() != null && resourcePolicyService.isDateValid(resourcePolicy)) { groups.add(resourcePolicy.getGroup()); diff --git a/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksum.java b/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksum.java index 5962d19f68..eff8a8be1c 100644 --- a/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksum.java +++ b/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksum.java @@ -170,7 +170,7 @@ public class MostRecentChecksum implements Serializable { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !(o instanceof MostRecentChecksum)) { return false; } diff --git a/dspace-api/src/main/java/org/dspace/checker/ResultsLogger.java b/dspace-api/src/main/java/org/dspace/checker/ResultsLogger.java index 358d0c4018..f95778c4a8 100644 --- a/dspace-api/src/main/java/org/dspace/checker/ResultsLogger.java +++ b/dspace-api/src/main/java/org/dspace/checker/ResultsLogger.java @@ -109,7 +109,7 @@ public class ResultsLogger implements ChecksumResultsCollector { "unknown")); LOG.info(msg("new-checksum") + ": " + info.getCurrentChecksum()); LOG.info(msg("checksum-comparison-result") + ": " - + (info.getChecksumResult().getResultCode())); + + info.getChecksumResult().getResultCode()); LOG.info("\n\n"); } } diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java b/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java index 5cf787ffd5..6d64ee3073 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamFormat.java @@ -9,7 +9,7 @@ package org.dspace.content; import java.io.Serializable; import java.sql.SQLException; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import javax.persistence.CollectionTable; import javax.persistence.Column; @@ -111,7 +111,7 @@ public class BitstreamFormat implements Serializable, ReloadableEntity * {@link org.dspace.content.service.BitstreamFormatService#create(Context)} */ protected BitstreamFormat() { - fileExtensions = new LinkedList<>(); + fileExtensions = new ArrayList<>(); } /** diff --git a/dspace-api/src/main/java/org/dspace/content/Bundle.java b/dspace-api/src/main/java/org/dspace/content/Bundle.java index 88f21c2c2f..6c62c3dc91 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bundle.java +++ b/dspace-api/src/main/java/org/dspace/content/Bundle.java @@ -9,7 +9,6 @@ package org.dspace.content; import java.sql.SQLException; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; @@ -138,7 +137,7 @@ public class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport { * @return the bitstreams */ public List getBitstreams() { - List bitstreamList = new LinkedList<>(this.bitstreams); + List bitstreamList = new ArrayList<>(this.bitstreams); return bitstreamList; } @@ -191,7 +190,7 @@ public class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport { @Override public boolean equals(Object obj) { - if (obj == null) { + if (obj == null || !(obj instanceof Bundle)) { return false; } Class objClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj); @@ -202,10 +201,7 @@ public class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport { if (this.getType() != other.getType()) { return false; } - if (!this.getID().equals(other.getID())) { - return false; - } - return true; + return this.getID().equals(other.getID()); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/DCDate.java b/dspace-api/src/main/java/org/dspace/content/DCDate.java index 47ed805d4a..d58aff7b1e 100644 --- a/dspace-api/src/main/java/org/dspace/content/DCDate.java +++ b/dspace-api/src/main/java/org/dspace/content/DCDate.java @@ -34,8 +34,8 @@ import org.apache.logging.log4j.Logger; * There are four levels of granularity, depending on how much date information * is available: year, month, day, time. *

    - * Examples: 1994-05-03T15:30:24,1995-10-04, - * 2001-10,1975 + * Examples: {@code 1994-05-03T15:30:24}, {@code 1995-10-04}, + * {@code 2001-10}, {@code 1975} * * @author Robert Tansley * @author Larry Stone @@ -261,7 +261,7 @@ public class DCDate { * @return the year */ public int getYear() { - return (!withinGranularity(DateGran.YEAR)) ? -1 : localCalendar.get(Calendar.YEAR); + return !withinGranularity(DateGran.YEAR) ? -1 : localCalendar.get(Calendar.YEAR); } /** @@ -270,7 +270,7 @@ public class DCDate { * @return the month */ public int getMonth() { - return (!withinGranularity(DateGran.MONTH)) ? -1 : localCalendar.get(Calendar.MONTH) + 1; + return !withinGranularity(DateGran.MONTH) ? -1 : localCalendar.get(Calendar.MONTH) + 1; } /** @@ -279,7 +279,7 @@ public class DCDate { * @return the day */ public int getDay() { - return (!withinGranularity(DateGran.DAY)) ? -1 : localCalendar.get(Calendar.DAY_OF_MONTH); + return !withinGranularity(DateGran.DAY) ? -1 : localCalendar.get(Calendar.DAY_OF_MONTH); } /** @@ -288,7 +288,7 @@ public class DCDate { * @return the hour */ public int getHour() { - return (!withinGranularity(DateGran.TIME)) ? -1 : localCalendar.get(Calendar.HOUR_OF_DAY); + return !withinGranularity(DateGran.TIME) ? -1 : localCalendar.get(Calendar.HOUR_OF_DAY); } /** @@ -297,7 +297,7 @@ public class DCDate { * @return the minute */ public int getMinute() { - return (!withinGranularity(DateGran.TIME)) ? -1 : localCalendar.get(Calendar.MINUTE); + return !withinGranularity(DateGran.TIME) ? -1 : localCalendar.get(Calendar.MINUTE); } /** @@ -306,7 +306,7 @@ public class DCDate { * @return the second */ public int getSecond() { - return (!withinGranularity(DateGran.TIME)) ? -1 : localCalendar.get(Calendar.SECOND); + return !withinGranularity(DateGran.TIME) ? -1 : localCalendar.get(Calendar.SECOND); } /** @@ -315,7 +315,7 @@ public class DCDate { * @return the year */ public int getYearUTC() { - return (!withinGranularity(DateGran.YEAR)) ? -1 : calendar.get(Calendar.YEAR); + return !withinGranularity(DateGran.YEAR) ? -1 : calendar.get(Calendar.YEAR); } /** @@ -324,7 +324,7 @@ public class DCDate { * @return the month */ public int getMonthUTC() { - return (!withinGranularity(DateGran.MONTH)) ? -1 : calendar.get(Calendar.MONTH) + 1; + return !withinGranularity(DateGran.MONTH) ? -1 : calendar.get(Calendar.MONTH) + 1; } /** @@ -333,7 +333,7 @@ public class DCDate { * @return the day */ public int getDayUTC() { - return (!withinGranularity(DateGran.DAY)) ? -1 : calendar.get(Calendar.DAY_OF_MONTH); + return !withinGranularity(DateGran.DAY) ? -1 : calendar.get(Calendar.DAY_OF_MONTH); } /** @@ -342,7 +342,7 @@ public class DCDate { * @return the hour */ public int getHourUTC() { - return (!withinGranularity(DateGran.TIME)) ? -1 : calendar.get(Calendar.HOUR_OF_DAY); + return !withinGranularity(DateGran.TIME) ? -1 : calendar.get(Calendar.HOUR_OF_DAY); } /** @@ -351,7 +351,7 @@ public class DCDate { * @return the minute */ public int getMinuteUTC() { - return (!withinGranularity(DateGran.TIME)) ? -1 : calendar.get(Calendar.MINUTE); + return !withinGranularity(DateGran.TIME) ? -1 : calendar.get(Calendar.MINUTE); } /** @@ -360,10 +360,9 @@ public class DCDate { * @return the second */ public int getSecondUTC() { - return (!withinGranularity(DateGran.TIME)) ? -1 : calendar.get(Calendar.SECOND); + return !withinGranularity(DateGran.TIME) ? -1 : calendar.get(Calendar.SECOND); } - /** * Get the date as a string to put back in the Dublin Core. Use the UTC/GMT calendar version. * diff --git a/dspace-api/src/main/java/org/dspace/content/DCSeriesNumber.java b/dspace-api/src/main/java/org/dspace/content/DCSeriesNumber.java index bec81494be..37b9fb7d7d 100644 --- a/dspace-api/src/main/java/org/dspace/content/DCSeriesNumber.java +++ b/dspace-api/src/main/java/org/dspace/content/DCSeriesNumber.java @@ -8,10 +8,9 @@ package org.dspace.content; /** - * Series and report number, as stored in relation.ispartofseries + * Series and report number, as stored in {@code relation.ispartofseries}. * * @author Robert Tansley - * @version $Id$ */ public class DCSeriesNumber { /** @@ -70,6 +69,7 @@ public class DCSeriesNumber { * * @return the series and number as they should be stored in the DB */ + @Override public String toString() { if (series == null) { return (null); diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index 90893e030d..961e59d264 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -13,7 +13,6 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -450,7 +449,7 @@ public abstract class DSpaceObjectServiceImpl implements clearMetadata(context, dso, field.schema, field.element, field.qualifier, language); - String newValueLanguage = (Item.ANY.equals(language)) ? null : language; + String newValueLanguage = Item.ANY.equals(language) ? null : language; addMetadata(context, dso, field.schema, field.element, field.qualifier, newValueLanguage, value); dso.setMetadataModified(); @@ -595,7 +594,7 @@ public abstract class DSpaceObjectServiceImpl implements */ // A map created to store the latest place for each metadata field Map fieldToLastPlace = new HashMap<>(); - List metadataValues = new LinkedList<>(); + List metadataValues; if (dso.getType() == Constants.ITEM) { metadataValues = getMetadata(dso, Item.ANY, Item.ANY, Item.ANY, Item.ANY); } else { @@ -628,7 +627,7 @@ public abstract class DSpaceObjectServiceImpl implements String authority = metadataValue.getAuthority(); String relationshipId = StringUtils.split(authority, "::")[1]; Relationship relationship = relationshipService.find(context, Integer.parseInt(relationshipId)); - if (relationship.getLeftItem() == (Item) dso) { + if (relationship.getLeftItem().equals((Item) dso)) { relationship.setLeftPlace(mvPlace); } else { relationship.setRightPlace(mvPlace); diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java index d44ec5a35d..20ab758a0b 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityType.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java @@ -78,6 +78,7 @@ public class EntityType implements ReloadableEntity { * * @return The ID for this EntityType */ + @Override public Integer getID() { return id; } @@ -87,6 +88,7 @@ public class EntityType implements ReloadableEntity { * @param obj object to be compared * @return */ + @Override public boolean equals(Object obj) { if (!(obj instanceof EntityType)) { return false; @@ -97,10 +99,7 @@ public class EntityType implements ReloadableEntity { return false; } - if (!StringUtils.equals(this.getLabel(), entityType.getLabel())) { - return false; - } - return true; + return StringUtils.equals(this.getLabel(), entityType.getLabel()); } /** diff --git a/dspace-api/src/main/java/org/dspace/content/Relationship.java b/dspace-api/src/main/java/org/dspace/content/Relationship.java index 3a2aad5bdf..f4bcf78a79 100644 --- a/dspace-api/src/main/java/org/dspace/content/Relationship.java +++ b/dspace-api/src/main/java/org/dspace/content/Relationship.java @@ -218,6 +218,7 @@ public class Relationship implements ReloadableEntity { * Standard getter for the ID for this Relationship * @return The ID of this relationship */ + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java index 5f16870bc6..676b7fd2a8 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java @@ -201,7 +201,7 @@ public class RelationshipType implements ReloadableEntity { /** * Standard setter for the leftMinCardinality Integer for this RelationshipType - * @param leftMinCardinality The leftMinCardinality Integer that this RelationshipType should recieve + * @param leftMinCardinality The leftMinCardinality Integer that this RelationshipType should receive */ public void setLeftMinCardinality(Integer leftMinCardinality) { this.leftMinCardinality = leftMinCardinality; @@ -217,7 +217,7 @@ public class RelationshipType implements ReloadableEntity { /** * Standard setter for the leftMaxCardinality Integer for this RelationshipType - * @param leftMaxCardinality The leftMaxCardinality Integer that this RelationshipType should recieve + * @param leftMaxCardinality The leftMaxCardinality Integer that this RelationshipType should receive */ public void setLeftMaxCardinality(Integer leftMaxCardinality) { this.leftMaxCardinality = leftMaxCardinality; @@ -233,7 +233,7 @@ public class RelationshipType implements ReloadableEntity { /** * Standard setter for the rightMinCardinality Integer for this RelationshipType - * @param rightMinCardinality The rightMinCardinality Integer that this RelationshipType should recieve + * @param rightMinCardinality The rightMinCardinality Integer that this RelationshipType should receive */ public void setRightMinCardinality(Integer rightMinCardinality) { this.rightMinCardinality = rightMinCardinality; @@ -249,7 +249,7 @@ public class RelationshipType implements ReloadableEntity { /** * Standard setter for the rightMaxCardinality Integer for this RelationshipType - * @param rightMaxCardinality The rightMaxCardinality Integer that this RelationshipType should recieve + * @param rightMaxCardinality The rightMaxCardinality Integer that this RelationshipType should receive */ public void setRightMaxCardinality(Integer rightMaxCardinality) { this.rightMaxCardinality = rightMaxCardinality; @@ -291,6 +291,7 @@ public class RelationshipType implements ReloadableEntity { * Standard getter for the ID of this RelationshipType * @return The ID of this RelationshipType */ + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataSchemaDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataSchemaDAOImpl.java index 80198a1e89..71eb487b83 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataSchemaDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/MetadataSchemaDAOImpl.java @@ -8,7 +8,7 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; @@ -63,7 +63,7 @@ public class MetadataSchemaDAOImpl extends AbstractHibernateDAO Root metadataSchemaRoot = criteriaQuery.from(MetadataSchema.class); criteriaQuery.select(metadataSchemaRoot); - List orderList = new LinkedList<>(); + List orderList = new ArrayList<>(); orderList.add(criteriaBuilder.asc(metadataSchemaRoot.get(MetadataSchema_.id))); criteriaQuery.orderBy(orderList); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageException.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageException.java index 58f2621afb..13c705c932 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageException.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageException.java @@ -19,7 +19,6 @@ import org.apache.logging.log4j.Logger; * exceptions. This class is intended for declarations and catch clauses. * * @author Larry Stone - * @version $Revision$ */ public class PackageException extends Exception { /** @@ -76,10 +75,4 @@ public class PackageException extends Exception { log.error(sw.toString()); } } - - public String toString() { - String base = getClass().getName() + ": " + getMessage(); - return (getCause() == null) ? base : - base + ", Reason: " + getCause().toString(); - } } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageParameters.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageParameters.java index 1bd68bea26..b472a52c3b 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageParameters.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageParameters.java @@ -57,7 +57,7 @@ public class PackageParameters extends Properties { } else if (v.length == 1) { result.setProperty(name, v[0]); } else { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < v.length; ++i) { if (i > 0) { sb.append(SEPARATOR); diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/UUIDValue.java b/dspace-api/src/main/java/org/dspace/content/virtual/UUIDValue.java index 252faf019c..0b08cc309f 100644 --- a/dspace-api/src/main/java/org/dspace/content/virtual/UUIDValue.java +++ b/dspace-api/src/main/java/org/dspace/content/virtual/UUIDValue.java @@ -8,7 +8,6 @@ package org.dspace.content.virtual; import java.sql.SQLException; -import java.util.LinkedList; import java.util.List; import org.dspace.content.Item; @@ -24,9 +23,7 @@ public class UUIDValue implements VirtualMetadataConfiguration { @Override public List getValues(Context context, Item item) throws SQLException { - List list = new LinkedList<>(); - list.add(String.valueOf(item.getID())); - return list; + return List.of(String.valueOf(item.getID())); } @Override diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 2181626250..be52595c09 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -8,13 +8,13 @@ package org.dspace.core; import java.sql.SQLException; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.EmptyStackException; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Set; -import java.util.Stack; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -79,13 +79,13 @@ public class Context implements AutoCloseable { /** * A stack with the history of authorisation system check modify */ - private Stack authStateChangeHistory; + private ArrayDeque authStateChangeHistory; /** * A stack with the name of the caller class that modify authorisation * system check */ - private Stack authStateClassCallHistory; + private ArrayDeque authStateClassCallHistory; /** * Group IDs of special groups user is a member of @@ -115,7 +115,7 @@ public class Context implements AutoCloseable { /** * Cache that is only used the context is in READ_ONLY mode */ - private ContextReadOnlyCache readOnlyCache = new ContextReadOnlyCache(); + private final ContextReadOnlyCache readOnlyCache = new ContextReadOnlyCache(); protected EventService eventService; @@ -183,8 +183,8 @@ public class Context implements AutoCloseable { specialGroups = new ArrayList<>(); - authStateChangeHistory = new Stack<>(); - authStateClassCallHistory = new Stack<>(); + authStateChangeHistory = new ArrayDeque<>(); + authStateClassCallHistory = new ArrayDeque<>(); setMode(this.mode); } @@ -336,7 +336,7 @@ public class Context implements AutoCloseable { + previousCaller)); } } - ignoreAuth = previousState.booleanValue(); + ignoreAuth = previousState; } /** @@ -488,7 +488,7 @@ public class Context implements AutoCloseable { throw new IllegalStateException("Attempt to mutate object in read-only context"); } if (events == null) { - events = new LinkedList(); + events = new LinkedList<>(); } events.add(event); @@ -622,11 +622,7 @@ public class Context implements AutoCloseable { * @return true if member */ public boolean inSpecialGroup(UUID groupID) { - if (specialGroups.contains(groupID)) { - return true; - } - - return false; + return specialGroups.contains(groupID); } /** @@ -636,7 +632,7 @@ public class Context implements AutoCloseable { * @throws SQLException if database error */ public List getSpecialGroups() throws SQLException { - List myGroups = new ArrayList(); + List myGroups = new ArrayList<>(); for (UUID groupId : specialGroups) { myGroups.add(EPersonServiceFactory.getInstance().getGroupService().find(this, groupId)); } @@ -661,7 +657,7 @@ public class Context implements AutoCloseable { currentUserPreviousState = currentUser; specialGroupsPreviousState = specialGroups; - specialGroups = new ArrayList(); + specialGroups = new ArrayList<>(); currentUser = newUser; } @@ -703,11 +699,13 @@ public class Context implements AutoCloseable { /** - * Returns the size of the cache of all object that have been read from the database so far. A larger number - * means that more memory is consumed by the cache. This also has a negative impact on the query performance. In - * that case you should consider uncaching entities when they are no longer needed (see - * {@link Context#uncacheEntity(ReloadableEntity)} () uncacheEntity}). + * Returns the size of the cache of all object that have been read from the + * database so far. A larger number means that more memory is consumed by + * the cache. This also has a negative impact on the query performance. In + * that case you should consider uncaching entities when they are no longer + * needed (see {@link Context#uncacheEntity(ReloadableEntity)} () uncacheEntity}). * + * @return cache size. * @throws SQLException When connecting to the active cache fails. */ public long getCacheSize() throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java index 68db217f1e..8c101a8f05 100644 --- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java +++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java @@ -320,11 +320,11 @@ public class I18nUtil { fileType = ""; } - if (!("".equals(locale.getCountry()))) { + if (!"".equals(locale.getCountry())) { fileNameLC = fileName + "_" + locale.getLanguage() + "_" + locale.getCountry(); - if (!("".equals(locale.getVariant()))) { + if (!"".equals(locale.getVariant())) { fileNameLCV = fileName + "_" + locale.getLanguage() + "_" + locale.getCountry() + "_" + locale.getVariant(); } diff --git a/dspace-api/src/main/java/org/dspace/curate/FileTaskQueue.java b/dspace-api/src/main/java/org/dspace/curate/FileTaskQueue.java index 979ade99a5..f603fa2e9a 100644 --- a/dspace-api/src/main/java/org/dspace/curate/FileTaskQueue.java +++ b/dspace-api/src/main/java/org/dspace/curate/FileTaskQueue.java @@ -13,16 +13,17 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.services.factory.DSpaceServicesFactory; - /** * FileTaskQueue provides a TaskQueue implementation based on flat files * for the queues and semaphores. @@ -30,14 +31,16 @@ import org.dspace.services.factory.DSpaceServicesFactory; * @author richardrodgers */ public class FileTaskQueue implements TaskQueue { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(TaskQueue.class); + private static final Logger log = LogManager.getLogger(TaskQueue.class); + // base directory for curation task queues protected String tqDir; // ticket for queue readers protected long readTicket = -1L; + // list of queues owned by reader - protected List readList = new ArrayList(); + protected List readList = new ArrayList<>(); public FileTaskQueue() { tqDir = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("curate.taskqueue.dir"); @@ -72,7 +75,7 @@ public class FileTaskQueue implements TaskQueue { BufferedWriter writer = null; try { File queue = new File(qDir, "queue" + Integer.toString(queueIdx)); - writer = new BufferedWriter(new FileWriter(queue, true)); + writer = new BufferedWriter(new FileWriter(queue, StandardCharsets.UTF_8, true)); Iterator iter = entrySet.iterator(); while (iter.hasNext()) { writer.write(iter.next().toString()); @@ -96,7 +99,7 @@ public class FileTaskQueue implements TaskQueue { @Override public synchronized Set dequeue(String queueName, long ticket) throws IOException { - Set entrySet = new HashSet(); + Set entrySet = new HashSet<>(); if (readTicket == -1L) { // hold the ticket & copy all Ids available, locking queues // stop when no more queues or one found locked @@ -113,8 +116,8 @@ public class FileTaskQueue implements TaskQueue { // read contents from file BufferedReader reader = null; try { - reader = new BufferedReader(new FileReader(queue)); - String entryStr = null; + reader = new BufferedReader(new FileReader(queue, StandardCharsets.UTF_8)); + String entryStr; while ((entryStr = reader.readLine()) != null) { entryStr = entryStr.trim(); if (entryStr.length() > 0) { diff --git a/dspace-api/src/main/java/org/dspace/curate/ResolvedTask.java b/dspace-api/src/main/java/org/dspace/curate/ResolvedTask.java index 89e92609f0..0b05ab3e0f 100644 --- a/dspace-api/src/main/java/org/dspace/curate/ResolvedTask.java +++ b/dspace-api/src/main/java/org/dspace/curate/ResolvedTask.java @@ -24,7 +24,7 @@ public class ResolvedTask { private CurationTask cTask; private ScriptedTask sTask; // local name of task - private String taskName; + private final String taskName; // annotation data private boolean distributive = false; private boolean mutative = false; @@ -76,7 +76,7 @@ public class ResolvedTask { * @throws IOException if error */ public int perform(DSpaceObject dso) throws IOException { - return (unscripted()) ? cTask.perform(dso) : sTask.performDso(dso); + return unscripted() ? cTask.perform(dso) : sTask.performDso(dso); } /** @@ -88,7 +88,7 @@ public class ResolvedTask { * @throws IOException if error */ public int perform(Context ctx, String id) throws IOException { - return (unscripted()) ? cTask.perform(ctx, id) : sTask.performId(ctx, id); + return unscripted() ? cTask.perform(ctx, id) : sTask.performId(ctx, id); } /** diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java index d82779015f..f9ca9c2e9c 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java +++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java @@ -33,9 +33,9 @@ public class DiscoverQuery { * Main attributes for the discovery query **/ private String query; - private List filterQueries; + private final List filterQueries; private List dspaceObjectFilters = new ArrayList<>(); - private List fieldPresentQueries; + private final List fieldPresentQueries; private boolean spellCheck; private int start = 0; @@ -55,36 +55,35 @@ public class DiscoverQuery { /** * Attributes required for the faceting of values **/ - private List facetFields; - private List facetQueries; - private int facetLimit = -1; + private final List facetFields; + private final List facetQueries; private int facetMinCount = -1; private int facetOffset = 0; - private Map hitHighlighting; + private final Map hitHighlighting; /** * Used when you want to search for a specific field value **/ - private List searchFields; + private final List searchFields; /** * Misc attributes can be implementation dependent **/ - private Map> properties; + private final Map> properties; private String discoveryConfigurationName; public DiscoverQuery() { //Initialize all our lists - this.filterQueries = new ArrayList(); - this.fieldPresentQueries = new ArrayList(); + this.filterQueries = new ArrayList<>(); + this.fieldPresentQueries = new ArrayList<>(); - this.facetFields = new ArrayList(); - this.facetQueries = new ArrayList(); - this.searchFields = new ArrayList(); - this.hitHighlighting = new HashMap(); + this.facetFields = new ArrayList<>(); + this.facetQueries = new ArrayList<>(); + this.searchFields = new ArrayList<>(); + this.hitHighlighting = new HashMap<>(); //Use a linked hashmap since sometimes insertion order might matter - this.properties = new LinkedHashMap>(); + this.properties = new LinkedHashMap<>(); } @@ -309,7 +308,7 @@ public class DiscoverQuery { public void addProperty(String property, String value) { List toAddList = properties.get(property); if (toAddList == null) { - toAddList = new ArrayList(); + toAddList = new ArrayList<>(); } toAddList.add(value); @@ -322,7 +321,7 @@ public class DiscoverQuery { } public List getHitHighlightingFields() { - return new ArrayList(hitHighlighting.values()); + return new ArrayList<>(hitHighlighting.values()); } public void addHitHighlightingField(DiscoverHitHighlightingField hitHighlighting) { @@ -368,7 +367,7 @@ public class DiscoverQuery { private List buildFacetQueriesWithGap(int newestYear, int oldestYear, String dateFacet, int gap, int topYear, int facetLimit) { - List facetQueries = new LinkedList<>(); + List facetQueries = new ArrayList<>(); for (int year = topYear; year > oldestYear && (facetQueries.size() < facetLimit); year -= gap) { //Add a filter to remove the last year only if we aren't the last year int bottomYear = year - gap; @@ -392,7 +391,7 @@ public class DiscoverQuery { } private int getTopYear(int newestYear, int gap) { - return (int) (Math.ceil((float) (newestYear) / gap) * gap); + return (int) (Math.ceil((float) newestYear / gap) * gap); } /** diff --git a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java index b3ee42fca1..ee220e5a4f 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java +++ b/dspace-api/src/main/java/org/dspace/discovery/FullTextContentStreams.java @@ -179,6 +179,11 @@ public class FullTextContentStreams extends ContentStreamBase { } } + /** + * {@link Enumeration} is implemented because instances of this class are + * passed to a JDK class that requires this obsolete type. + */ + @SuppressWarnings("JdkObsolete") private static class FullTextEnumeration implements Enumeration { private final Iterator fulltextIterator; diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java index 3f5e765b0e..52e0043ff4 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java @@ -55,7 +55,7 @@ public class SolrServiceFileInfoPlugin implements SolrServiceIndexPlugin { document.addField(SOLR_FIELD_NAME_FOR_FILENAMES, bitstream.getName()); String description = bitstream.getDescription(); - if ((description != null) && (!description.isEmpty())) { + if ((description != null) && !description.isEmpty()) { document.addField(SOLR_FIELD_NAME_FOR_DESCRIPTIONS, description); } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java index 90aafcbd30..21c9a66b9f 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java @@ -14,10 +14,13 @@ import org.dspace.discovery.IndexableObject; /** * This class exists in order to provide a default implementation for the equals and hashCode methods. - * Since IndexableObjects can be made multiple times for the same underlying object, we needed a more finetuned - * equals and hashcode methods. We're simply checking that the underlying objects are equal and generating the hashcode - * for the underlying object. This way, we'll always get a proper result when calling equals or hashcode on an - * IndexableObject because it'll depend on the underlying object + * Since IndexableObjects can be made multiple times for the same underlying + * object, we needed more finely-tuned {@link equals} and {@link hashCode} methods. + * We're simply checking that the underlying objects are equal and returning the + * hash-code for the underlying object. This way, we'll always get a proper + * result when calling {@link equals} or {@link hashCode} on an IndexableObject + * because it'll depend on the underlying object. + * * @param Refers to the underlying entity that is linked to this object * @param The type of ID that this entity uses */ @@ -30,7 +33,7 @@ public abstract class AbstractIndexableObject, PK if (!(obj instanceof AbstractIndexableObject)) { return false; } - IndexableDSpaceObject other = (IndexableDSpaceObject) obj; + AbstractIndexableObject other = (AbstractIndexableObject) obj; return other.getIndexedObject().equals(getIndexedObject()); } 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 abe8ad481c..1719888ca8 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -53,6 +53,7 @@ public class Subscription implements ReloadableEntity { } + @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 a90c5da5a1..40d031ac29 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 @@ -8,6 +8,7 @@ package org.dspace.eperson.dao.impl; import java.sql.SQLException; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import javax.persistence.Query; @@ -97,7 +98,7 @@ public class SubscriptionDAOImpl extends AbstractHibernateDAO impl Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); - List orderList = new LinkedList<>(); + List orderList = new ArrayList<>(1); orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.ePerson))); criteriaQuery.orderBy(orderList); diff --git a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java index c04f64f674..f6f5f6cfd9 100644 --- a/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OrcidRestConnector.java @@ -8,6 +8,7 @@ package org.dspace.external; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Scanner; import org.apache.commons.lang3.StringUtils; @@ -15,6 +16,7 @@ import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** @@ -28,9 +30,9 @@ public class OrcidRestConnector { /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OrcidRestConnector.class); + private static final Logger log = LogManager.getLogger(OrcidRestConnector.class); - private String url; + private final String url; public OrcidRestConnector(String url) { this.url = url; @@ -73,7 +75,7 @@ public class OrcidRestConnector { } public static String convertStreamToString(InputStream is) { - Scanner s = new Scanner(is).useDelimiter("\\A"); + Scanner s = new Scanner(is, StandardCharsets.UTF_8).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } diff --git a/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java b/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java index cc2587056c..eac9921df6 100644 --- a/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java +++ b/dspace-api/src/main/java/org/dspace/external/model/ExternalDataObject.java @@ -7,7 +7,7 @@ */ package org.dspace.external.model; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import org.dspace.content.dto.MetadataValueDTO; @@ -32,7 +32,7 @@ public class ExternalDataObject { /** * The list of Metadata values. These our MetadataValueDTO because they won't exist in the DB */ - private List metadata = new LinkedList<>(); + private List metadata = new ArrayList<>(); /** * The display value of the ExternalDataObject */ @@ -87,11 +87,11 @@ public class ExternalDataObject { /** * This method will add a Metadata value to the list of metadata values - * @param metadataValueDTO The metadatavalue to be added + * @param metadataValueDTO The metadata value to be added. */ public void addMetadata(MetadataValueDTO metadataValueDTO) { if (metadata == null) { - metadata = new LinkedList<>(); + metadata = new ArrayList<>(); } metadata.add(metadataValueDTO); } diff --git a/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java b/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java index 582c49d407..8f48cda712 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/orcid/xml/Converter.java @@ -13,7 +13,6 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; -import org.apache.logging.log4j.Logger; import org.xml.sax.SAXException; /** @@ -25,11 +24,6 @@ import org.xml.sax.SAXException; */ public abstract class Converter { - /** - * log4j logger - */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(Converter.class); - public abstract T convert(InputStream document); protected Object unmarshall(InputStream input, Class type) throws SAXException, URISyntaxException { diff --git a/dspace-api/src/main/java/org/dspace/harvest/HarvestedItem.java b/dspace-api/src/main/java/org/dspace/harvest/HarvestedItem.java index 87d2a58749..343347136b 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/HarvestedItem.java +++ b/dspace-api/src/main/java/org/dspace/harvest/HarvestedItem.java @@ -56,6 +56,7 @@ public class HarvestedItem implements ReloadableEntity { protected HarvestedItem() { } + @Override public Integer getID() { return id; } @@ -89,7 +90,6 @@ public class HarvestedItem implements ReloadableEntity { */ public void setOaiID(String itemOaiID) { this.oaiId = itemOaiID; - return; } diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOI.java b/dspace-api/src/main/java/org/dspace/identifier/DOI.java index b73fb2b155..67668c3abe 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOI.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOI.java @@ -62,6 +62,7 @@ public class DOI protected DOI() { } + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java b/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java index 019e89c129..13c53d12f7 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java @@ -8,7 +8,7 @@ package org.dspace.identifier.dao.impl; import java.sql.SQLException; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -24,7 +24,7 @@ import org.dspace.identifier.dao.DOIDAO; /** * Hibernate implementation of the Database Access Object interface class for the DOI object. - * This class is responsible for all database calls for the DOI object and is autowired by spring + * This class is responsible for all database calls for the DOI object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -52,7 +52,7 @@ public class DOIDAOImpl extends AbstractHibernateDAO implements DOIDAO { Root doiRoot = criteriaQuery.from(DOI.class); criteriaQuery.select(doiRoot); - List listToIncludeInOrPredicate = new LinkedList<>(); + List listToIncludeInOrPredicate = new ArrayList<>(statusToExclude.size() + 1); for (Integer status : statusToExclude) { listToIncludeInOrPredicate.add(criteriaBuilder.notEqual(doiRoot.get(DOI_.status), status)); @@ -75,7 +75,7 @@ public class DOIDAOImpl extends AbstractHibernateDAO implements DOIDAO { CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, DOI.class); Root doiRoot = criteriaQuery.from(DOI.class); criteriaQuery.select(doiRoot); - List orPredicates = new LinkedList<>(); + List orPredicates = new ArrayList<>(statuses.size()); for (Integer status : statuses) { orPredicates.add(criteriaBuilder.equal(doiRoot.get(DOI_.status), status)); } @@ -92,13 +92,13 @@ public class DOIDAOImpl extends AbstractHibernateDAO implements DOIDAO { Root doiRoot = criteriaQuery.from(DOI.class); criteriaQuery.select(doiRoot); - List listToIncludeInOrPredicate = new LinkedList<>(); + List listToIncludeInOrPredicate = new ArrayList<>(excludedStatuses.size()); for (Integer status : excludedStatuses) { listToIncludeInOrPredicate.add(criteriaBuilder.notEqual(doiRoot.get(DOI_.status), status)); } - List listToIncludeInAndPredicate = new LinkedList<>(); + List listToIncludeInAndPredicate = new ArrayList<>(); listToIncludeInAndPredicate.add(criteriaBuilder.like(doiRoot.get(DOI_.doi), doi)); listToIncludeInAndPredicate.add(criteriaBuilder.or(listToIncludeInOrPredicate.toArray(new Predicate[] {}))); @@ -107,8 +107,6 @@ public class DOIDAOImpl extends AbstractHibernateDAO implements DOIDAO { } criteriaQuery.where(listToIncludeInAndPredicate.toArray(new Predicate[] {})); return list(context, criteriaQuery, false, DOI.class, -1, -1); - - } @Override diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java index f5d3a6f722..3fc34dc511 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datamodel/ImportRecord.java @@ -7,9 +7,9 @@ */ package org.dspace.importer.external.datamodel; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import org.dspace.importer.external.metadatamapping.MetadatumDTO; @@ -38,7 +38,7 @@ public class ImportRecord { */ public ImportRecord(List valueList) { //don't want to alter the original list. Also now I can control the type of list - this.valueList = new LinkedList<>(valueList); + this.valueList = new ArrayList<>(valueList); } /** @@ -81,7 +81,7 @@ public class ImportRecord { * @return the MetadatumDTO's that are related to a given schema/element/qualifier pair/triplet */ public Collection getValue(String schema, String element, String qualifier) { - List values = new LinkedList(); + List values = new ArrayList(); for (MetadatumDTO value : valueList) { if (value.getSchema().equals(schema) && value.getElement().equals(element)) { if (qualifier == null && value.getQualifier() == null) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/AbstractMetadataFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/AbstractMetadataFieldMapping.java index aed2f0e084..d2f0df6d04 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/AbstractMetadataFieldMapping.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/AbstractMetadataFieldMapping.java @@ -7,18 +7,19 @@ */ package org.dspace.importer.external.metadatamapping; +import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.importer.external.metadatamapping.contributor.MetadataContributor; import org.dspace.importer.external.metadatamapping.transform.MetadataProcessorService; /** - * Abstract class that implements {@link MetadataFieldMapping} - * This class adds a default implementation for the MetadataFieldMapping methods + * Abstract class that implements {@link MetadataFieldMapping}. + * This class adds a default implementation for the MetadataFieldMapping methods. * * @author Roeland Dillen (roeland at atmire dot com) */ @@ -30,7 +31,7 @@ public abstract class AbstractMetadataFieldMapping /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(AbstractMetadataFieldMapping.class); + private static final Logger log = LogManager.getLogger(AbstractMetadataFieldMapping.class); /* A map containing what processing has to be done on a given metadataFieldConfig. * The processing of a value is used to determine the actual value that will be returned used. @@ -66,6 +67,7 @@ public abstract class AbstractMetadataFieldMapping * @param value The value to map to a MetadatumDTO * @return A metadatumDTO created from the field and value */ + @Override public MetadatumDTO toDCValue(MetadataFieldConfig field, String value) { MetadatumDTO dcValue = new MetadatumDTO(); @@ -108,14 +110,15 @@ public abstract class AbstractMetadataFieldMapping } /** - * Loop over the MetadataContributors and return their concatenated retrieved metadatumDTO objects + * Loop over the MetadataContributors and return their concatenated + * retrieved metadatumDTO objects. * * @param record Used to retrieve the MetadatumDTO * @return Lit of metadatumDTO */ @Override public Collection resultToDCValueMapping(RecordType record) { - List values = new LinkedList(); + List values = new ArrayList<>(); for (MetadataContributor query : getMetadataFieldMap().values()) { try { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/MetadataFieldConfig.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/MetadataFieldConfig.java index d19939248c..be3c85ab62 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/MetadataFieldConfig.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/MetadataFieldConfig.java @@ -8,7 +8,7 @@ package org.dspace.importer.external.metadatamapping; /** - * A generalised configuration for metadatafields. + * A generalised configuration for metadata fields. * This is used to make the link between values and the actual MetadatumDTO object. * * @author Roeland Dillen (roeland at atmire dot com) @@ -31,7 +31,7 @@ public class MetadataFieldConfig { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !(o instanceof MetadataFieldConfig)) { return false; } @@ -43,11 +43,7 @@ public class MetadataFieldConfig { if (qualifier != null ? !qualifier.equals(that.qualifier) : that.qualifier != null) { return false; } - if (!schema.equals(that.schema)) { - return false; - } - - return true; + return schema.equals(that.schema); } /** diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceCommandLineParameter.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceCommandLineParameter.java index 2862d014c9..f8b5f23b8c 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceCommandLineParameter.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceCommandLineParameter.java @@ -14,7 +14,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; /** - * This class serves as a representation of a command line parameter by holding a String name and a String value + * This class serves as a representation of a command line parameter by holding + * a String name and a String value. */ public class DSpaceCommandLineParameter { private String name; @@ -23,7 +24,7 @@ public class DSpaceCommandLineParameter { public static String SEPARATOR = "|||"; /** - * This constructor will take a String key and String value and store them in their appriopriate fields + * This constructor will take a String key and String value and store them in their appropriate fields. * @param key The String value to be stored as the name of the parameter * @param value The String value to be stored as the value of the parameter */ @@ -64,9 +65,10 @@ public class DSpaceCommandLineParameter { /** * Converts the DSpaceCommandLineParameter into a String format by concatenating the value and the name String - * values by separating them with a space + * values by separating them with a space. * @return The String representation of a DSpaceCommandlineParameter object */ + @Override public String toString() { String stringToReturn = ""; stringToReturn += getName(); @@ -92,7 +94,7 @@ public class DSpaceCommandLineParameter { } /** - * Will return a boolean indicating whether the given param is equal to this object + * Will return a boolean indicating whether the given parameter is equal to this object. * @param other The other object * @return A boolean indicating equality */ @@ -101,7 +103,7 @@ public class DSpaceCommandLineParameter { if (other == null) { return false; } - if (other.getClass() != DSpaceCommandLineParameter.class) { + if (!(other instanceof DSpaceCommandLineParameter)) { return false; } return StringUtils.equals(this.getName(), ((DSpaceCommandLineParameter) other).getName()) && StringUtils diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index d0fffdb57d..2319aee317 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -8,7 +8,7 @@ package org.dspace.scripts; import java.io.InputStream; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -37,7 +37,7 @@ public abstract class DSpaceRunnable implements R protected CommandLine commandLine; /** - * This EPerson identifier variable is the uuid of the eperson that's running the script + * This EPerson identifier variable is the UUID of the EPerson that's running the script */ private UUID epersonIdentifier; @@ -129,7 +129,7 @@ public abstract class DSpaceRunnable implements R * @return The list of Strings representing filenames from the options given to the script */ public List getFileNamesFromInputStreamOptions() { - List fileNames = new LinkedList<>(); + List fileNames = new ArrayList<>(); for (Option option : getScriptConfiguration().getOptions().getOptions()) { if (option.getType() == InputStream.class && @@ -151,8 +151,8 @@ public abstract class DSpaceRunnable implements R } /** - * Generic setter for the epersonIdentifier - * This EPerson identifier variable is the uuid of the eperson that's running the script + * Generic setter for the epersonIdentifier. + * This EPerson identifier variable is the UUID of the EPerson that's running the script. * @param epersonIdentifier The epersonIdentifier to be set on this DSpaceRunnable */ public void setEpersonIdentifier(UUID epersonIdentifier) { 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 574ba59760..2f5ab10f3e 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -7,8 +7,8 @@ */ package org.dspace.scripts; +import java.util.Collections; import java.util.Date; -import java.util.LinkedList; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; @@ -91,6 +91,7 @@ public class Process implements ReloadableEntity { * This method returns the ID that the Process holds within the Database * @return The ID that the process holds within the database */ + @Override public Integer getID() { return processId; } @@ -162,7 +163,8 @@ public class Process implements ReloadableEntity { /** * To get the parameters, use ProcessService.getParameters() to get a parsed list of DSpaceCommandLineParameters - * This String representation is the parameter in an unparsed fashion. For example "-c test" + * This String representation is the parameter in an unparsed fashion.For example "-c test" + * @return the raw parameter string. */ protected String getParameters() { return parameters; @@ -179,7 +181,7 @@ public class Process implements ReloadableEntity { */ public List getBitstreams() { if (bitstreams == null) { - bitstreams = new LinkedList<>(); + bitstreams = Collections.EMPTY_LIST; } return bitstreams; } diff --git a/dspace-api/src/main/java/org/dspace/search/Harvest.java b/dspace-api/src/main/java/org/dspace/search/Harvest.java index a0b3698592..b0bafd367f 100644 --- a/dspace-api/src/main/java/org/dspace/search/Harvest.java +++ b/dspace-api/src/main/java/org/dspace/search/Harvest.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import java.text.ParseException; import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import org.apache.logging.log4j.Logger; @@ -129,7 +128,7 @@ public class Harvest { // several smaller operations (e.g. for OAI resumption tokens.) discoverQuery.setSortField("search.resourceid", DiscoverQuery.SORT_ORDER.asc); - List infoObjects = new LinkedList(); + List infoObjects = new ArrayList<>(); // Count of items read from the record set that match the selection criteria. // Note : Until 'index > offset' the records are not added to the output set. @@ -155,7 +154,7 @@ public class Harvest { if (collections) { // Add collections data - fillCollections(context, itemInfo); + fillCollections(itemInfo); } if (items) { @@ -163,7 +162,7 @@ public class Harvest { itemInfo.item = itemService.find(context, itemInfo.itemID); } - if ((nonAnon) || (itemInfo.item == null) || (withdrawn && itemInfo.withdrawn)) { + if (nonAnon || (itemInfo.item == null) || (withdrawn && itemInfo.withdrawn)) { index++; if (index > offset) { infoObjects.add(itemInfo); @@ -221,7 +220,7 @@ public class Harvest { // Get the sets if (collections) { - fillCollections(context, itemInfo); + fillCollections(itemInfo); } return itemInfo; @@ -234,8 +233,7 @@ public class Harvest { * @param itemInfo HarvestedItemInfo object to fill out * @throws SQLException if database error */ - private static void fillCollections(Context context, - HarvestedItemInfo itemInfo) throws SQLException { + private static void fillCollections(HarvestedItemInfo itemInfo) throws SQLException { // Get the collection Handles from DB List collections = itemInfo.item.getCollections(); itemInfo.collectionHandles = new ArrayList<>(); diff --git a/dspace-api/src/main/java/org/dspace/statistics/Dataset.java b/dspace-api/src/main/java/org/dspace/statistics/Dataset.java index 9204c56a21..f5a5e05e04 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/Dataset.java +++ b/dspace-api/src/main/java/org/dspace/statistics/Dataset.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import au.com.bytecode.opencsv.CSVWriter; +import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -65,20 +66,20 @@ public class Dataset { } private void initRowLabels(int rows) { - rowLabels = new ArrayList(rows); - rowLabelsAttrs = new ArrayList>(); + rowLabels = new ArrayList<>(rows); + rowLabelsAttrs = new ArrayList<>(); for (int i = 0; i < rows; i++) { rowLabels.add("Row " + (i + 1)); - rowLabelsAttrs.add(new HashMap()); + rowLabelsAttrs.add(new HashMap<>()); } } private void initColumnLabels(int nbCols) { - colLabels = new ArrayList(nbCols); - colLabelsAttrs = new ArrayList>(); + colLabels = new ArrayList<>(nbCols); + colLabelsAttrs = new ArrayList<>(); for (int i = 0; i < nbCols; i++) { colLabels.add("Column " + (i + 1)); - colLabelsAttrs.add(new HashMap()); + colLabelsAttrs.add(new HashMap<>()); } } @@ -232,7 +233,7 @@ public class Dataset { public ByteArrayOutputStream exportAsCSV() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CSVWriter ecsvp = new CSVWriter(new OutputStreamWriter(baos), ';'); + CSVWriter ecsvp = new CSVWriter(new OutputStreamWriter(baos, StandardCharsets.UTF_8), ';'); //Generate the item row List colLabels = getColLabels(); colLabels.add(0, ""); diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerUsageEventListener.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerUsageEventListener.java index bcb8657ff2..5f29d84e54 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerUsageEventListener.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerUsageEventListener.java @@ -7,9 +7,10 @@ */ package org.dspace.statistics; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.eperson.EPerson; import org.dspace.services.model.Event; @@ -28,7 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class SolrLoggerUsageEventListener extends AbstractUsageEventListener { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrLoggerUsageEventListener.class); + private static final Logger log = LogManager.getLogger(SolrLoggerUsageEventListener.class); protected SolrLoggerService solrLoggerService; @@ -56,7 +57,7 @@ public class SolrLoggerUsageEventListener extends AbstractUsageEventListener { } } else if (UsageEvent.Action.SEARCH == ue.getAction()) { UsageSearchEvent usageSearchEvent = (UsageSearchEvent) ue; - List queries = new LinkedList<>(); + List queries = new ArrayList<>(); queries.add(usageSearchEvent.getQuery()); solrLoggerService.postSearch(usageSearchEvent.getObject(), usageSearchEvent.getRequest(), currentUser, queries, usageSearchEvent.getPage().getSize(), diff --git a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java index 4197fc74af..4ee7a0f3e4 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java +++ b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java @@ -285,13 +285,8 @@ public class StatisticsDataVisits extends StatisticsData { DatasetQuery firsDataset = datasetQueries.get(0); //Do the first query - ObjectCount[] topCounts1 = null; -// if (firsDataset.getQueries().size() == 1) { - topCounts1 = + ObjectCount[] topCounts1 = queryFacetField(firsDataset, firsDataset.getQueries().get(0).getQuery(), filterQuery, facetMinCount); -// } else { -// TODO: do this -// } // Check if we have more queries that need to be done if (datasetQueries.size() == 2) { DatasetQuery secondDataSet = datasetQueries.get(1); @@ -313,7 +308,6 @@ public class StatisticsDataVisits extends StatisticsData { } for (int i = 0; i < topCounts1.length; i++) { ObjectCount count1 = topCounts1[i]; - ObjectCount[] currentResult = new ObjectCount[topCounts2.length]; // Make sure we have a dataSet if (dataset == null) { @@ -645,7 +639,7 @@ public class StatisticsDataVisits extends StatisticsData { // be null if a handle has not yet been assigned. In this case reference the // item its internal id. In the last case where the bitstream is not associated // with an item (such as a community logo) then reference the bitstreamID directly. - String identifier = null; + String identifier; if (owningItem != null && owningItem.getHandle() != null) { identifier = "handle/" + owningItem.getHandle(); } else if (owningItem != null) { diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/ValueConcatenationModifier.java b/dspace-api/src/main/java/org/dspace/submit/lookup/ValueConcatenationModifier.java index 8d52f1ba73..f84a632e01 100644 --- a/dspace-api/src/main/java/org/dspace/submit/lookup/ValueConcatenationModifier.java +++ b/dspace-api/src/main/java/org/dspace/submit/lookup/ValueConcatenationModifier.java @@ -39,11 +39,11 @@ public class ValueConcatenationModifier extends AbstractModifier { public Record modify(MutableRecord rec) { List values = rec.getValues(field); if (values != null) { - List converted_values = new ArrayList(); + List converted_values = new ArrayList<>(); for (Value val : values) { converted_values.add(val.getAsString()); } - List final_value = new ArrayList(); + List final_value = new ArrayList<>(); String v = StringUtils.join(converted_values.iterator(), separator + (whitespaceAfter ? " " : "")); final_value.add(new StringValue(v)); @@ -89,9 +89,9 @@ public class ValueConcatenationModifier extends AbstractModifier { } /** - * @param whiteSpaceAfter the whiteSpaceAfter to set + * @param whitespaceAfter the whiteSpaceAfter to set */ - public void setWhitespaceAfter(boolean whiteSpaceAfter) { - this.whitespaceAfter = whiteSpaceAfter; + public void setWhitespaceAfter(boolean whitespaceAfter) { + this.whitespaceAfter = whitespaceAfter; } } diff --git a/dspace-api/src/main/java/org/dspace/submit/util/ItemSubmissionLookupDTO.java b/dspace-api/src/main/java/org/dspace/submit/util/ItemSubmissionLookupDTO.java index de144838ec..e187c9a02e 100644 --- a/dspace-api/src/main/java/org/dspace/submit/util/ItemSubmissionLookupDTO.java +++ b/dspace-api/src/main/java/org/dspace/submit/util/ItemSubmissionLookupDTO.java @@ -30,11 +30,9 @@ public class ItemSubmissionLookupDTO implements Serializable { private static final String MERGED_PUBLICATION_PROVIDER = "merged"; - private static final String UNKNOWN_PROVIDER_STRING = "UNKNOWN-PROVIDER"; + private final List publications; - private List publications; - - private String uuid; + private final String uuid; public ItemSubmissionLookupDTO(List publications) { this.uuid = UUID.randomUUID().toString(); @@ -46,7 +44,7 @@ public class ItemSubmissionLookupDTO implements Serializable { } public Set getProviders() { - Set orderedProviders = new LinkedHashSet(); + Set orderedProviders = new LinkedHashSet<>(); for (Record p : publications) { orderedProviders.add(SubmissionLookupService.getProviderName(p)); } diff --git a/dspace-api/src/main/java/org/dspace/versioning/Version.java b/dspace-api/src/main/java/org/dspace/versioning/Version.java index 2d4d359545..ee5c1c4183 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/Version.java +++ b/dspace-api/src/main/java/org/dspace/versioning/Version.java @@ -77,6 +77,7 @@ public class Version implements ReloadableEntity { } + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java index 1acacc7838..683504e3fe 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java @@ -20,7 +20,6 @@ import javax.persistence.OrderBy; import javax.persistence.SequenceGenerator; import javax.persistence.Table; -import org.apache.logging.log4j.Logger; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; import org.hibernate.proxy.HibernateProxyHelper; @@ -35,8 +34,6 @@ import org.hibernate.proxy.HibernateProxyHelper; @Table(name = "versionhistory") public class VersionHistory implements ReloadableEntity { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(VersionHistory.class); - @Id @Column(name = "versionhistory_id") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "versionhistory_seq") @@ -56,6 +53,7 @@ public class VersionHistory implements ReloadableEntity { } + @Override public Integer getID() { return id; } @@ -98,11 +96,7 @@ public class VersionHistory implements ReloadableEntity { } final VersionHistory that = (VersionHistory) o; - if (!this.getID().equals(that.getID())) { - return false; - } - - return true; + return this.getID().equals(that.getID()); } @Override diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowException.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowException.java index f05ab80486..3de660f279 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowException.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowException.java @@ -27,7 +27,8 @@ public class WorkflowException extends Exception { this.reason = reason; } - public String toString() { + @Override + public String getMessage() { return reason; } } diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowItem.java b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowItem.java index 065cae25fc..d744df2b1c 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowItem.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowItem.java @@ -116,6 +116,7 @@ public class BasicWorkflowItem implements WorkflowItem { * * @return state */ + @Override public int getState() { return state; } diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/TaskListItem.java b/dspace-api/src/main/java/org/dspace/workflowbasic/TaskListItem.java index 82a9438f68..f6edb8c99c 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/TaskListItem.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/TaskListItem.java @@ -73,6 +73,7 @@ public class TaskListItem implements ReloadableEntity { this.workflowItem = workflowItem; } + @Override public Integer getID() { return taskListItemId; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/RoleMembers.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/RoleMembers.java index 39e3110d63..869f744440 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/RoleMembers.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/RoleMembers.java @@ -30,8 +30,8 @@ import org.dspace.eperson.service.GroupService; public class RoleMembers { protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - private ArrayList groups; - private ArrayList epersons; + private final ArrayList groups; + private final ArrayList epersons; public RoleMembers() { this.groups = new ArrayList<>(); @@ -55,11 +55,7 @@ public class RoleMembers { } public void removeEperson(EPerson epersonToRemove) { - for (EPerson eperson : epersons) { - if (eperson.equals(epersonToRemove)) { - epersons.remove(eperson); - } - } + epersons.removeIf(eperson -> eperson.equals(epersonToRemove)); } public ArrayList getAllUniqueMembers(Context context) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowConfigurationException.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowConfigurationException.java index 5c2e1af487..e4e3ef759e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowConfigurationException.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/WorkflowConfigurationException.java @@ -8,7 +8,7 @@ package org.dspace.xmlworkflow; /** - * Exception for problems with the configuration xml + * Exception for problems with the configuration XML. * * @author Bram De Schouwer (bram.deschouwer at dot com) * @author Kevin Van de Velde (kevin at atmire dot com) @@ -17,13 +17,14 @@ package org.dspace.xmlworkflow; */ public class WorkflowConfigurationException extends Exception { - private String error; + private final String error; public WorkflowConfigurationException(String error) { this.error = error; } - public String toString() { + @Override + public String getMessage() { return this.error; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java index 8882055f82..8f4794cb3b 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/ClaimedTask.java @@ -71,6 +71,7 @@ public class ClaimedTask implements ReloadableEntity { } + @Override public Integer getID() { return id; } @@ -91,24 +92,24 @@ public class ClaimedTask implements ReloadableEntity { return workflowItem; } - public void setActionID(String actionID) { - this.actionId = actionID; + public void setActionID(String actionId) { + this.actionId = actionId; } public String getActionID() { return actionId; } - public void setStepID(String stepID) { - this.stepId = stepID; + public void setStepID(String stepId) { + this.stepId = stepId; } public String getStepID() { return stepId; } - public void setWorkflowID(String workflowID) { - this.workflowId = workflowID; + public void setWorkflowID(String workflowId) { + this.workflowId = workflowId; } public String getWorkflowID() { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/CollectionRole.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/CollectionRole.java index 114db17087..c9a7995e03 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/CollectionRole.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/CollectionRole.java @@ -91,6 +91,7 @@ public class CollectionRole implements ReloadableEntity { return group; } + @Override public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java index 2a87de51cb..9cfc9ea068 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/PoolTask.java @@ -78,12 +78,13 @@ public class PoolTask implements ReloadableEntity { } + @Override public Integer getID() { return id; } - public void setEperson(EPerson eperson) { - this.ePerson = eperson; + public void setEperson(EPerson ePerson) { + this.ePerson = ePerson; } public EPerson getEperson() { @@ -114,16 +115,16 @@ public class PoolTask implements ReloadableEntity { return this.workflowItem; } - public void setStepID(String stepID) { - this.stepId = stepID; + public void setStepID(String stepId) { + this.stepId = stepId; } public String getStepID() { return stepId; } - public void setActionID(String actionID) { - this.actionId = actionID; + public void setActionID(String actionId) { + this.actionId = actionId; } public String getActionID() { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRole.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRole.java index 9a7e5a034c..cc6df9731b 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRole.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/WorkflowItemRole.java @@ -69,7 +69,7 @@ public class WorkflowItemRole implements ReloadableEntity { } - + @Override public Integer getID() { return id; } @@ -90,8 +90,8 @@ public class WorkflowItemRole implements ReloadableEntity { return workflowItem; } - public void setEPerson(EPerson eperson) { - this.ePerson = eperson; + public void setEPerson(EPerson ePerson) { + this.ePerson = ePerson; } public EPerson getEPerson() throws SQLException { diff --git a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTest.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTest.java index d437a77385..725f9d2783 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTest.java @@ -12,6 +12,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; @@ -118,7 +119,8 @@ public class AbstractIntegrationTest extends AbstractUnitTest { */ protected void appendToLocalConfiguration(String textToAppend) { String extraConfPath = getLocalConfigurationFilePath(); - try (Writer output = new BufferedWriter(new FileWriter(extraConfPath, true))) { + try (Writer output = new BufferedWriter( + new FileWriter(extraConfPath, StandardCharsets.UTF_8, true))) { output.append("\n"); output.append(textToAppend); output.flush(); diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index 3dd229f066..1ea7479c6b 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -47,10 +47,12 @@ import org.junit.Test; public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { - private ItemService itemService - = ContentServiceFactory.getInstance().getItemService(); - private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - private RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + private final ItemService itemService + = ContentServiceFactory.getInstance().getItemService(); + private final EPersonService ePersonService + = EPersonServiceFactory.getInstance().getEPersonService(); + private final RelationshipService relationshipService + = ContentServiceFactory.getInstance().getRelationshipService(); Collection collection; @@ -119,7 +121,7 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { Item importedItem = findItemByName("Test Import 1"); - assertEquals(relationshipService.findByItem(context, importedItem).size(), 1); + assertEquals(1, relationshipService.findByItem(context, importedItem).size()); context.turnOffAuthorisationSystem(); itemService.delete(context, itemService.find(context, importedItem.getID())); context.restoreAuthSystemState(); @@ -148,7 +150,7 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { Item importedItem = findItemByName("Person1"); - assertEquals(relationshipService.findByItem(context, importedItem).size(), 1); + assertEquals(1, relationshipService.findByItem(context, importedItem).size()); } diff --git a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java index 2dfe3a781f..538240b0f6 100644 --- a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java +++ b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java @@ -56,14 +56,16 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat //Common collection to utilize for test private Collection col1; - private RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); - private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); - + private final RelationshipService relationshipService + = ContentServiceFactory.getInstance().getRelationshipService(); + private final ItemService itemService + = ContentServiceFactory.getInstance().getItemService(); Community parentCommunity; /** - * Setup testing enviorment + * Setup testing environment. + * @throws java.sql.SQLException passed through. */ @Before public void setup() throws SQLException { @@ -80,7 +82,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); EntityType person = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); EntityType project = EntityTypeBuilder.createEntityTypeBuilder(context, "Project").build(); - EntityType orgUnit = EntityTypeBuilder.createEntityTypeBuilder(context, "OrgUnit").build(); + EntityTypeBuilder.createEntityTypeBuilder(context, "OrgUnit").build(); RelationshipTypeBuilder .createRelationshipTypeBuilder(context, publication, person, "isAuthorOfPublication", @@ -350,11 +352,12 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat /** * Test failure when referring to item by non unique metadata in the database. + * @throws java.lang.Exception passed through. */ @Test(expected = MetadataImportException.class) public void testNonUniqueMDRefInDb() throws Exception { context.turnOffAuthorisationSystem(); - Item person = ItemBuilder.createItem(context, col1) + ItemBuilder.createItem(context, col1) .withTitle("Person") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") @@ -363,7 +366,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat .withRelationshipType("Person") .withIdentifierOther("1") .build(); - Item person2 = ItemBuilder.createItem(context, col1) + ItemBuilder.createItem(context, col1) .withTitle("Person2") .withIssueDate("2017-10-17") .withAuthor("Smith, John") @@ -385,7 +388,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat @Test(expected = MetadataImportException.class) public void testNonUniqueMDRefInBoth() throws Exception { context.turnOffAuthorisationSystem(); - Item person = ItemBuilder.createItem(context, col1) + ItemBuilder.createItem(context, col1) .withTitle("Person") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") @@ -402,7 +405,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat } /** - * Test failure when refering to item by metadata that does not exist in the relation column + * Test failure when referring to item by metadata that does not exist in the relation column */ @Test(expected = Exception.class) public void testNonExistMdRef() throws Exception { @@ -413,7 +416,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat } /** - * Test failure when refering to an item in the CSV that hasn't been created yet due to it's order in the CSV + * Test failure when referring to an item in the CSV that hasn't been created yet due to it's order in the CSV */ @Test(expected = Exception.class) public void testCSVImportWrongOrder() throws Exception { @@ -424,7 +427,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat } /** - * Test failure when refering to an item in the CSV that hasn't been created yet due to it's order in the CSV + * Test failure when referring to an item in the CSV that hasn't been created yet due to it's order in the CSV */ @Test(expected = Exception.class) public void testCSVImportWrongOrderRowName() throws Exception { @@ -446,7 +449,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat } /** - * Test relationship validation with invalid relationship definition and with an archived origin referer + * Test relationship validation with invalid relationship definition and with an archived origin referrer. */ @Test(expected = MetadataImportInvalidHeadingException.class) public void testInvalidRelationshipArchivedOrigin() throws Exception { diff --git a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index 973710eff0..a6d7fc1353 100644 --- a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -7,8 +7,8 @@ */ package org.dspace.builder.util; +import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -51,24 +51,24 @@ public class AbstractBuilderCleanupUtil { } private void initMap() { - map.put(RelationshipBuilder.class.getName(), new LinkedList<>()); - map.put(RelationshipTypeBuilder.class.getName(), new LinkedList<>()); - map.put(EntityTypeBuilder.class.getName(), new LinkedList<>()); - map.put(PoolTaskBuilder.class.getName(), new LinkedList<>()); - map.put(WorkflowItemBuilder.class.getName(), new LinkedList<>()); - map.put(WorkspaceItemBuilder.class.getName(), new LinkedList<>()); - map.put(BitstreamBuilder.class.getName(), new LinkedList<>()); - map.put(BitstreamFormatBuilder.class.getName(), new LinkedList<>()); - map.put(ClaimedTaskBuilder.class.getName(), new LinkedList<>()); - map.put(CollectionBuilder.class.getName(), new LinkedList<>()); - map.put(CommunityBuilder.class.getName(), new LinkedList<>()); - map.put(EPersonBuilder.class.getName(), new LinkedList<>()); - map.put(GroupBuilder.class.getName(), new LinkedList<>()); - map.put(ItemBuilder.class.getName(), new LinkedList<>()); - map.put(MetadataFieldBuilder.class.getName(), new LinkedList<>()); - map.put(MetadataSchemaBuilder.class.getName(), new LinkedList<>()); - map.put(SiteBuilder.class.getName(), new LinkedList<>()); - map.put(ProcessBuilder.class.getName(), new LinkedList<>()); + map.put(RelationshipBuilder.class.getName(), new ArrayList<>()); + map.put(RelationshipTypeBuilder.class.getName(), new ArrayList<>()); + map.put(EntityTypeBuilder.class.getName(), new ArrayList<>()); + map.put(PoolTaskBuilder.class.getName(), new ArrayList<>()); + map.put(WorkflowItemBuilder.class.getName(), new ArrayList<>()); + map.put(WorkspaceItemBuilder.class.getName(), new ArrayList<>()); + map.put(BitstreamBuilder.class.getName(), new ArrayList<>()); + map.put(BitstreamFormatBuilder.class.getName(), new ArrayList<>()); + map.put(ClaimedTaskBuilder.class.getName(), new ArrayList<>()); + map.put(CollectionBuilder.class.getName(), new ArrayList<>()); + map.put(CommunityBuilder.class.getName(), new ArrayList<>()); + map.put(EPersonBuilder.class.getName(), new ArrayList<>()); + map.put(GroupBuilder.class.getName(), new ArrayList<>()); + map.put(ItemBuilder.class.getName(), new ArrayList<>()); + map.put(MetadataFieldBuilder.class.getName(), new ArrayList<>()); + map.put(MetadataSchemaBuilder.class.getName(), new ArrayList<>()); + map.put(SiteBuilder.class.getName(), new ArrayList<>()); + map.put(ProcessBuilder.class.getName(), new ArrayList<>()); } /** @@ -78,7 +78,7 @@ public class AbstractBuilderCleanupUtil { * @param abstractBuilder The AbstractBuilder to be added */ public void addToMap(AbstractBuilder abstractBuilder) { - map.computeIfAbsent(abstractBuilder.getClass().getName(), k -> new LinkedList<>()).add(abstractBuilder); + map.computeIfAbsent(abstractBuilder.getClass().getName(), k -> new ArrayList<>()).add(abstractBuilder); } /** diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataFieldNameTest.java b/dspace-api/src/test/java/org/dspace/content/MetadataFieldNameTest.java index a3a069b35e..60b43479c7 100644 --- a/dspace-api/src/test/java/org/dspace/content/MetadataFieldNameTest.java +++ b/dspace-api/src/test/java/org/dspace/content/MetadataFieldNameTest.java @@ -40,8 +40,9 @@ public class MetadataFieldNameTest { } @Test(expected = NullPointerException.class) + @SuppressWarnings("ResultOfObjectAllocationIgnored") public void testConstructNull() { - MetadataFieldName instance = new MetadataFieldName("one", null); + new MetadataFieldName("one", null); } /** @@ -71,7 +72,7 @@ public class MetadataFieldNameTest { */ @Test(expected = IllegalArgumentException.class) public void TestParse1() { - String[] results = MetadataFieldName.parse("one"); + MetadataFieldName.parse("one"); } /** @@ -79,15 +80,16 @@ public class MetadataFieldNameTest { */ @Test(expected = IllegalArgumentException.class) public void TestParse0() { - String[] results = MetadataFieldName.parse(""); + MetadataFieldName.parse(""); } /** * Test of parse method using an illegal null name. */ @Test(expected = NullPointerException.class) + @SuppressWarnings("null") public void TestParseNull() { - String[] results = MetadataFieldName.parse(null); + MetadataFieldName.parse(null); } /** 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 77cf105dd4..7ade9c582d 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 @@ -13,7 +13,6 @@ import static org.junit.Assert.assertNotNull; import java.io.IOException; import org.dspace.AbstractDSpaceTest; -import org.dspace.content.Collection; import org.dspace.core.factory.CoreServiceFactory; import org.junit.After; import org.junit.AfterClass; @@ -66,6 +65,8 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest { /** * Test of getMatches method, of class DSpaceControlledVocabulary. + * @throws java.io.IOException passed through. + * @throws java.lang.ClassNotFoundException passed through. */ @Test public void testGetMatches() throws IOException, ClassNotFoundException { @@ -74,9 +75,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest { final String PLUGIN_INTERFACE = "org.dspace.content.authority.ChoiceAuthority"; // Ensure that 'id' attribute is optional - String field = null; // not used String text = "north 40"; - Collection collection = null; int start = 0; int limit = 10; String locale = null; diff --git a/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java b/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java index bc687a43f5..8545c4187d 100644 --- a/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java @@ -9,8 +9,8 @@ package org.dspace.license; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -51,7 +51,7 @@ public class MockCCLicenseConnectorServiceImpl extends CCLicenseConnectorService } private List createMockLicenseFields(int count, int[] amountOfFieldsAndEnums) { - List ccLicenseFields = new LinkedList<>(); + List ccLicenseFields = new ArrayList<>(amountOfFieldsAndEnums.length); for (int index = 0; index < amountOfFieldsAndEnums.length; index++) { String licenseFieldId = "license" + count + "-field" + index; String licenseFieldLabel = "License " + count + " - Field " + index + " - Label"; @@ -70,7 +70,7 @@ public class MockCCLicenseConnectorServiceImpl extends CCLicenseConnectorService } private List createMockLicenseFields(int count, int index, int amountOfEnums) { - List ccLicenseFieldEnumList = new LinkedList<>(); + List ccLicenseFieldEnumList = new ArrayList<>(amountOfEnums); for (int i = 0; i < amountOfEnums; i++) { String enumId = "license" + count + "-field" + index + "-enum" + i; String enumLabel = "License " + count + " - Field " + index + " - Enum " + i + " - Label"; diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java index 0b8c28a7b0..901780f821 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java @@ -65,7 +65,7 @@ import org.junit.Test; //@RunWith(MockitoJUnitRunner.class) public class ITIrusExportUsageEventListener extends AbstractIntegrationTestWithDatabase { - private static Logger log = Logger.getLogger(ITIrusExportUsageEventListener.class); + private static final Logger log = Logger.getLogger(ITIrusExportUsageEventListener.class); protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); @@ -86,7 +86,7 @@ public class ITIrusExportUsageEventListener extends AbstractIntegrationTestWithD .getServiceByName("testProcessedUrls", ArrayList.class); - private IrusExportUsageEventListener exportUsageEventListener = + private final IrusExportUsageEventListener exportUsageEventListener = DSpaceServicesFactory.getInstance() .getServiceManager() .getServicesByType(IrusExportUsageEventListener.class) @@ -105,9 +105,11 @@ public class ITIrusExportUsageEventListener extends AbstractIntegrationTestWithD /** - * Initializes the test by setting up all objects needed to create a test item + * Initializes the test by setting up all objects needed to create a test item. + * @throws java.lang.Exception passed through. */ @Before() + @Override public void setUp() throws Exception { super.setUp(); @@ -152,11 +154,12 @@ public class ITIrusExportUsageEventListener extends AbstractIntegrationTestWithD } /** - * Clean up the created objects - * Empty the testProcessedUrls used to store succeeded urls - * Empty the database table where the failed urls are logged + * Clean up the created objects. + * Empty the testProcessedUrls used to store succeeded URLs. + * Empty the database table where the failed URLs are logged. */ @After + @Override public void destroy() throws Exception { try { context.turnOffAuthorisationSystem(); @@ -377,11 +380,13 @@ public class ITIrusExportUsageEventListener extends AbstractIntegrationTestWithD /** * Test that an object that is not an Item or Bitstream is not processed + * @throws java.sql.SQLException passed through. */ @Test + @SuppressWarnings("ResultOfMethodCallIgnored") public void testReceiveEventOnNonRelevantObject() throws SQLException { - HttpServletRequest request = mock(HttpServletRequest.class); + mock(HttpServletRequest.class); UsageEvent usageEvent = mock(UsageEvent.class); when(usageEvent.getObject()).thenReturn(community); @@ -394,7 +399,6 @@ public class ITIrusExportUsageEventListener extends AbstractIntegrationTestWithD assertEquals(0, all.size()); assertEquals(0, testProcessedUrls.size()); - } /** @@ -408,11 +412,6 @@ public class ITIrusExportUsageEventListener extends AbstractIntegrationTestWithD Pattern p = Pattern.compile(regex); - if (p.matcher(string).matches()) { - return true; - } - return false; + return p.matcher(string).matches(); } - - } diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorTest.java index 3b197b4955..6a4bf895b3 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/processor/BitstreamEventProcessorTest.java @@ -37,13 +37,13 @@ import org.junit.Test; */ public class BitstreamEventProcessorTest extends AbstractIntegrationTestWithDatabase { - private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - + private final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); private String encodedUrl; - @Before + @Override public void setUp() throws Exception { super.setUp(); configurationService.setProperty("irus.statistics.tracker.enabled", true); diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java index 14ac9d36d5..a5fbfd029b 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/MockOpenUrlServiceImpl.java @@ -15,7 +15,8 @@ import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; /** - * Mock OpenUrlService that will ensure that IRUS tracker does need to be contacted in order to test the functionality + * Mock OpenUrlService that will ensure that IRUS tracker does need to be + * contacted in order to test the functionality. */ public class MockOpenUrlServiceImpl extends OpenUrlServiceImpl { @@ -23,13 +24,14 @@ public class MockOpenUrlServiceImpl extends OpenUrlServiceImpl { ArrayList testProcessedUrls; /** - * Returns a response code to simulate contact to the external url - * When the url contains "fail", a fail code 500 will be returned - * Otherwise the success code 200 will be returned + * Returns a response code to simulate contact to the external URL. + * When the URL contains "fail", a fail code 500 will be returned. + * Otherwise the success code 200 will be returned. * @param urlStr - * @return 200 or 500 depending on whether the "fail" keyword is present in the url + * @return 200 or 500 depending on whether the "fail" keyword is present in the URL. * @throws IOException */ + @Override protected int getResponseCodeFromUrl(final String urlStr) throws IOException { if (StringUtils.contains(urlStr, "fail")) { return HttpURLConnection.HTTP_INTERNAL_ERROR; From a036b99f1c3483bc26d6c7660925f537d5f41f7e Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 30 Nov 2020 16:21:01 -0500 Subject: [PATCH 0011/1254] Catch correct error for replaced class, to fix test errors. Satisfy checkstyle. #3061 --- .../src/main/java/org/dspace/core/Context.java | 15 ++++++--------- .../src/main/java/org/dspace/core/Utils.java | 2 +- .../java/org/dspace/discovery/DiscoverQuery.java | 5 ++--- .../eperson/dao/impl/SubscriptionDAOImpl.java | 1 - .../main/java/org/dspace/statistics/Dataset.java | 4 ++-- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index be52595c09..7bbea212c3 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -10,10 +10,10 @@ package org.dspace.core; import java.sql.SQLException; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.EmptyStackException; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.NoSuchElementException; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -157,8 +157,6 @@ public class Context implements AutoCloseable { /** * Initializes a new context object. - * - * @throws SQLException if there was an error obtaining a database connection */ protected void init() { updateDatabase(); @@ -310,7 +308,7 @@ public class Context implements AutoCloseable { Boolean previousState; try { previousState = authStateChangeHistory.pop(); - } catch (EmptyStackException ex) { + } catch (NoSuchElementException ex) { log.warn(LogManager.getHeader(this, "restore_auth_sys_state", "not previous state info available " + ex.getLocalizedMessage())); @@ -325,8 +323,7 @@ public class Context implements AutoCloseable { // if previousCaller is not the current caller *only* log a warning if (!previousCaller.equals(caller)) { - log - .warn(LogManager + log.warn(LogManager .getHeader( this, "restore_auth_sys_state", @@ -741,7 +738,7 @@ public class Context implements AutoCloseable { dbConnection.setConnectionMode(false, false); break; default: - log.warn("New context mode detected that has nog been configured."); + log.warn("New context mode detected that has not been configured."); break; } } catch (SQLException ex) { @@ -803,7 +800,7 @@ public class Context implements AutoCloseable { * entity. This means changes to the entity will be tracked and persisted to the database. * * @param entity The entity to reload - * @param The class of the enity. The entity must implement the {@link ReloadableEntity} interface. + * @param The class of the entity. The entity must implement the {@link ReloadableEntity} interface. * @return A (possibly) NEW reference to the entity that should be used for further processing. * @throws SQLException When reloading the entity from the database fails. */ @@ -816,7 +813,7 @@ public class Context implements AutoCloseable { * Remove an entity from the cache. This is necessary when batch processing a large number of items. * * @param entity The entity to reload - * @param The class of the enity. The entity must implement the {@link ReloadableEntity} interface. + * @param The class of the entity. The entity must implement the {@link ReloadableEntity} interface. * @throws SQLException When reloading the entity from the database fails. */ @SuppressWarnings("unchecked") diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index 8968c8950f..17fe7c4d03 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -17,6 +17,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.rmi.dgc.VMID; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -33,7 +34,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import com.coverity.security.Escape; -import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringSubstitutor; import org.apache.logging.log4j.LogManager; diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java index f9ca9c2e9c..e133ad0ed1 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java +++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverQuery.java @@ -15,7 +15,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -23,7 +22,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; /** - * This class represents a query which the discovery backend can use + * This class represents a query which the discovery back-end can use. * * @author Kevin Van de Velde (kevin at atmire dot com) */ @@ -273,7 +272,7 @@ public class DiscoverQuery { /** * Sets the fields which you want Discovery to return in the search results. * It is HIGHLY recommended to limit the fields returned, as by default - * some backends (like Solr) will return everything. + * some back-ends (like Solr) will return everything. * * @param field field to add to the list of fields returned */ 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 40d031ac29..6f2cb4b4fb 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,7 +9,6 @@ package org.dspace.eperson.dao.impl; 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; diff --git a/dspace-api/src/main/java/org/dspace/statistics/Dataset.java b/dspace-api/src/main/java/org/dspace/statistics/Dataset.java index f5a5e05e04..c1d6bc0dd6 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/Dataset.java +++ b/dspace-api/src/main/java/org/dspace/statistics/Dataset.java @@ -10,6 +10,7 @@ package org.dspace.statistics; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.text.ParseException; import java.util.ArrayList; @@ -18,13 +19,12 @@ import java.util.List; import java.util.Map; import au.com.bytecode.opencsv.CSVWriter; -import java.nio.charset.StandardCharsets; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; /** * @author kevinvandevelde at atmire.com - * Date: 21-jan-2009 + * Date: 21-Jan-2009 * Time: 13:44:48 */ public class Dataset { From c2c08b31094c9fdfc80a86c37eace5e782d4d48b Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 8 Dec 2020 08:07:54 -0500 Subject: [PATCH 0012/1254] Restore thread-safety to see if it is needed. #3061 --- dspace-api/src/main/java/org/dspace/core/Context.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 7bbea212c3..b45da5e633 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -8,14 +8,15 @@ package org.dspace.core; import java.sql.SQLException; -import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.logging.log4j.Logger; @@ -79,13 +80,13 @@ public class Context implements AutoCloseable { /** * A stack with the history of authorisation system check modify */ - private ArrayDeque authStateChangeHistory; + private Deque authStateChangeHistory; /** * A stack with the name of the caller class that modify authorisation * system check */ - private ArrayDeque authStateClassCallHistory; + private Deque authStateClassCallHistory; /** * Group IDs of special groups user is a member of @@ -181,8 +182,8 @@ public class Context implements AutoCloseable { specialGroups = new ArrayList<>(); - authStateChangeHistory = new ArrayDeque<>(); - authStateClassCallHistory = new ArrayDeque<>(); + authStateChangeHistory = new ConcurrentLinkedDeque<>(); + authStateClassCallHistory = new ConcurrentLinkedDeque<>(); setMode(this.mode); } From d27dc96cbef11c534d5c8e6706988c9d81ca13d7 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 11 Mar 2020 12:56:42 +1300 Subject: [PATCH 0013/1254] [DS-4522](main) Item logical filters and DOI filtered provider --- .../dspace/content/logic/DefaultFilter.java | 46 ++ .../java/org/dspace/content/logic/Filter.java | 35 ++ .../content/logic/LogicalStatement.java | 31 + .../logic/LogicalStatementException.java | 28 + .../dspace/content/logic/TestLogicRunner.java | 143 +++++ .../logic/condition/AbstractCondition.java | 84 +++ .../condition/BitstreamCountCondition.java | 72 +++ .../content/logic/condition/Condition.java | 55 ++ .../condition/InCollectionCondition.java | 59 ++ .../logic/condition/InCommunityCondition.java | 60 ++ .../logic/condition/IsWithdrawnCondition.java | 37 ++ .../MetadataValueMatchCondition.java | 66 ++ .../MetadataValuesMatchCondition.java | 77 +++ .../condition/ReadableByGroupCondition.java | 64 ++ .../logic/operator/AbstractOperator.java | 71 +++ .../dspace/content/logic/operator/And.java | 60 ++ .../dspace/content/logic/operator/Nand.java | 52 ++ .../dspace/content/logic/operator/Nor.java | 52 ++ .../dspace/content/logic/operator/Not.java | 69 +++ .../org/dspace/content/logic/operator/Or.java | 60 ++ .../identifier/DOIIdentifierProvider.java | 567 +++++++++++++----- .../FilteredIdentifierProvider.java | 93 +++ .../identifier/IdentifierServiceImpl.java | 43 +- .../DOIIdentifierNotApplicableException.java | 27 + .../dspace/identifier/doi/DOIOrganiser.java | 152 +++-- 25 files changed, 1907 insertions(+), 196 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/Filter.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/operator/And.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java create mode 100644 dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java create mode 100644 dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java 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 new file mode 100644 index 0000000000..eb4c4cff35 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.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.content.logic; + +import org.apache.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * The default filter, a very simple implementation of Filter / LogicalStatement + * The idea is to have this as a wrapper / root class for all logical operations, so it takes a single + * 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; + private static Logger log = Logger.getLogger(Filter.class); + + /** + * Set statement from Spring configuration in item-filters.xml + * Be aware that this is singular not plural. A filter can have one sub-statement only. + * + * @param statement LogicalStatement of this filter (operator, condition, or another filter) + */ + public void setStatement(LogicalStatement statement) { + this.statement = statement; + } + + /** + * Get the result of logical evaluation for an item + * @param context DSpace context + * @param item Item to evaluate + * @return boolean + * @throws LogicalStatementException + */ + public Boolean getResult(Context context, Item item) throws LogicalStatementException { + return this.statement.getResult(context, item); + } +} 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 new file mode 100644 index 0000000000..e156529651 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/Filter.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.content.logic; + +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * The interface for Filter currently doesn't add anything to LogicalStatement but inherits from it + * just to keep naming / reflection clean, and in case Filters should do anything additional in future. + * We need this as filters have to be specified in the spring configuration (item-filters.xml). + * Filters are the top level elements of the logic. Only logical statements that implement this interface + * are allowed to be the root element of a spring configuration (item-filters.xml) of this logic framework. + * A filter is just helping to differentiate between logical statement that can be used as root elements and + * logical statement that shouldn't be use as root element. A filter may contain only one substatement. + * + * @author Kim Shepherd + * @version $Revision$ + * @see org.dspace.content.logic.DefaultFilter + */ +public interface Filter extends LogicalStatement { + /** + * Get the result of logical evaluation for an item + * @param context DSpace context + * @param item Item to evaluate + * @return boolean + * @throws LogicalStatementException + */ + Boolean getResult(Context context, Item item) throws LogicalStatementException; +} 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 new file mode 100644 index 0000000000..38c1e298ee --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.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.content.logic; + +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * The base interface used by all logic classes: all operators and conditions are logical statements. + * All statements must accept an Item object and return a boolean result. + * The philosophy is that because Filter, Condition, Operator classes implement getResult(), they can all be + * used as sub-statements in other Filters and Operators. + * + * @author Kim Shepherd + * @version $Revision$ + */ +public interface LogicalStatement { + /** + * Get the result of logical evaluation for an item + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of evaluation + * @throws LogicalStatementException + */ + Boolean getResult(Context context, Item item) throws LogicalStatementException; +} 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 new file mode 100644 index 0000000000..9fa561a148 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java @@ -0,0 +1,28 @@ +package org.dspace.content.logic; + +/** + * Exception for errors encountered while evaluating logical statements + * defined as spring beans. + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class LogicalStatementException extends Exception { + + public LogicalStatementException() { + super(); + } + + public LogicalStatementException(String s, Throwable t) { + super(s, t); + } + + public LogicalStatementException(String s) { + super(s); + } + + public LogicalStatementException(Throwable t) { + super(t); + } + +} 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 new file mode 100644 index 0000000000..814c608a5e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java @@ -0,0 +1,143 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.sql.SQLException; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.apache.logging.log4j.Logger; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.kernel.ServiceManager; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * A command-line runner used for testing a logical filter against an item, or all items + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class TestLogicRunner { + + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(TestLogicRunner.class); + + /** + * Default constructor + */ + private TestLogicRunner() { } + + /** + * Main runner method for CLI usage + * @param argv array of command-line arguments + */ + public static void main(String[] argv) { + System.out.println("Starting impl of main() test spring logic item filter"); + + // initialize options + Options options = new Options(); + + options.addOption("h", "help", false, "Help"); + options.addOption("l", "list", false, "List filters"); + options.addOption("f", "filter", true, "Use filter "); + options.addOption("i","item", true, "Run filter over item "); + options.addOption("a","all", false, "Run filter over all items"); + + // initialize parser + CommandLineParser parser = new PosixParser(); + CommandLine line = null; + HelpFormatter helpformater = new HelpFormatter(); + + try { + line = parser.parse(options, argv); + } catch (ParseException ex) { + System.out.println(ex.getMessage()); + System.exit(1); + } + + if (line.hasOption("help")) { + helpformater.printHelp("\nTest the DSpace logical item filters\n", options); + System.exit(0); + } + + // Create a context + Context c = new Context(Context.Mode.READ_ONLY); + //c.turnOffAuthorisationSystem(); + ServiceManager manager = DSpaceServicesFactory.getInstance().getServiceManager(); + + if (line.hasOption("list")) { + // Lit filters and exit + List filters = manager.getServicesByType(Filter.class); + for (Filter filter : filters) { + System.out.println(filter.getClass().toString()); + } + System.out.println("See item-filters.xml spring config for filter names"); + System.exit(0); + } + + Filter filter; + + if (line.hasOption("filter")) { + String filterName = line.getOptionValue("filter"); + filter = manager.getServiceByName(filterName, Filter.class); + if (filter == null) { + System.out.println("Error loading filter: " + filterName); + System.exit(1); + } + + if (line.hasOption("item")) { + String handle = line.getOptionValue("item"); + + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + try { + DSpaceObject dso = handleService.resolveToObject(c, handle); + if (Constants.typeText[dso.getType()].equals("ITEM")) { + Item item = (Item) dso; + System.out.println(filter.getResult(c, item).toString()); + } else { + System.out.println(handle + " is not an ITEM"); + } + } catch (SQLException | LogicalStatementException e) { + System.out.println("Error encountered processing item " + handle + ": " + e.getMessage()); + } + + } else if (line.hasOption("all")) { + ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + try { + Iterator itemIterator = itemService.findAll(c); + while (itemIterator.hasNext()) { + Item i = itemIterator.next(); + System.out.println( + "Testing '" + filter + "' on item " + i.getHandle() + " ('" + i.getName() + "')" + ); + System.out.println(filter.getResult(c, i).toString()); + + } + } catch (SQLException | LogicalStatementException e) { + System.out.println("Error encountered processing items: " + e.getMessage()); + } + } else { + helpformater.printHelp("\nTest the DSpace logical item filters\n", options); + } + } + + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..8c474fbbfd --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.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.content.logic.condition; + +import java.util.HashMap; +import java.util.Map; + +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Abstract class for conditions, to implement the basic getter and setter parameters + * + * @author Kim Shepherd + * @version $Revision$ + */ +public abstract class AbstractCondition implements Condition { + private Map parameters = new HashMap<>(); + + @Autowired(required = true) + protected ItemService itemService; + @Autowired(required = true) + protected CollectionService collectionService; + @Autowired(required = true) + protected HandleService handleService; + + /** + * Get parameters set by spring configuration in item-filters.xml + * These could be any kind of map that the extending condition class needs for evaluation + * @return map of parameters + * @throws LogicalStatementException + */ + @Override + public Map getParameters() throws LogicalStatementException { + return this.parameters; + } + + /** + * Set parameters - used by Spring when creating beans from item-filters.xml + * These could be any kind of map that the extending condition class needs for evaluation + * @param parameters + * @throws LogicalStatementException + */ + @Override + public void setParameters(Map parameters) throws LogicalStatementException { + this.parameters = parameters; + } + + /** + * Get the result of logical evaluation for an item + * @param context DSpace context + * @param item Item to evaluate + * @return boolean + * @throws LogicalStatementException + */ + @Override + public Boolean getResult(Context context, Item item) throws LogicalStatementException { + if (item == null) { + throw new LogicalStatementException("Item is null"); + } + if (context == null) { + throw new LogicalStatementException("Context is null"); + } + if (this.parameters == null) { + throw new LogicalStatementException("Parameters are null"); + } + return true; + } + + @Override + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } +} 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 new file mode 100644 index 0000000000..dcb57c389e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.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.content.logic.condition; + +import java.util.List; + +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * 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 { + /** + * Return true if bitstream count is within bounds of min and/or max parameters + * Return false if out of bounds + * @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 { + + // This super call just throws some useful exceptions if required objects are null + super.getResult(context, item); + + int min = -1; + if (getParameters().get("min") != null) { + min = Integer.parseInt((String)getParameters().get("min")); + } + int max = -1; + if (getParameters().get("max") != null) { + max = Integer.parseInt((String)getParameters().get("max")); + } + String bundleName = (String)getParameters().get("bundle"); + if (min < 0 && max < 0) { + throw new LogicalStatementException("Either min or max parameter must be 0 or bigger."); + } + + List bundles; + int count = 0; + + if (bundleName != null) { + bundles = item.getBundles(bundleName); + } else { + bundles = item.getBundles(); + } + + for (Bundle bundle : bundles) { + count += bundle.getBitstreams().size(); + } + + if (min < 0) { + return (count <= max); + } + if (max < 0) { + return (count >= min); + } + return (count <= max && count >= min); + } +} 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 new file mode 100644 index 0000000000..c097263b03 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.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.content.logic.condition; + +import java.util.Map; + +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; + +/** + * The Condition interface + * + * A condition is one logical statement testing an item for any idea. A condition is always a logical statements. An + * operator is not a condition but also a logical statement. + * + * @author Kim Shepherd + * @version $Revision$ + */ +public interface Condition extends LogicalStatement { + + /** + * Set parameters - used by Spring + * @param parameters + * @throws LogicalStatementException + */ + void setParameters(Map parameters) throws LogicalStatementException; + + /** + * Get parameters set by Spring in item-filters.xml + * These could be any kind of map that the extending condition class needs for evaluation + * @return map of parameters + * @throws LogicalStatementException + */ + Map getParameters() throws LogicalStatementException; + + /** + * Get the result of logical evaluation for an item + * @param context DSpace context + * @param item Item to evaluate + * @return boolean + * @throws LogicalStatementException + */ + Boolean getResult(Context context, Item item) throws LogicalStatementException; + + public void setItemService(ItemService itemService); + +} 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 new file mode 100644 index 0000000000..78a257dd75 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.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.content.logic.condition; + +import java.util.List; + +import org.apache.log4j.Logger; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A condition that accepts a list of collection handles and returns true + * if the item belongs to any of them. + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class InCollectionCondition extends AbstractCondition { + private static Logger log = Logger.getLogger(InCollectionCondition.class); + + @Autowired(required = true) + protected CollectionService collectionService; + + /** + * Return true if item is in one of the specified collections + * 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 { + + List collectionHandles = (List)getParameters().get("collections"); + List itemCollections = item.getCollections(); + + for (Collection collection : itemCollections) { + if (collectionHandles.contains(collection.getHandle())) { + log.debug("item " + item.getHandle() + " is in collection " + + collection.getHandle() + ", returning true"); + return true; + } + } + + log.debug("item " + item.getHandle() + " not found in the passed collection handle list"); + + return false; + } +} 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 new file mode 100644 index 0000000000..a57fd2a5fd --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.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.content.logic.condition; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.log4j.Logger; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * A condition that accepts a list of community handles and returns true + * if the item belongs to any of them. + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class InCommunityCondition extends AbstractCondition { + private static Logger log = Logger.getLogger(InCommunityCondition.class); + + /** + * Return true if item is in one of the specified collections + * 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 { + + List communityHandles = (List)getParameters().get("communities"); + List itemCollections = item.getCollections(); + + for (Collection collection : itemCollections) { + try { + List communities = collection.getCommunities(); + for (Community community : communities) { + if (communityHandles.contains(community.getHandle())) { + return true; + } + } + } catch (SQLException e) { + log.error(e.getMessage()); + throw new LogicalStatementException(e); + } + } + + return false; + } +} 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 new file mode 100644 index 0000000000..ea3961744b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.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.content.logic.condition; + +import org.apache.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 IsWithdrawnCondition extends AbstractCondition { + private static Logger log = Logger.getLogger(IsWithdrawnCondition.class); + + /** + * 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.isWithdrawn()); + return item.isWithdrawn(); + } +} 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 new file mode 100644 index 0000000000..d39560c7a4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.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.content.logic.condition; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * A condition that returns true if a pattern (regex) matches any value + * in a given metadata field + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class MetadataValueMatchCondition extends AbstractCondition { + + private static Logger log = Logger.getLogger(MetadataValueMatchCondition.class); + + /** + * Return true if any value for a specified field in the item matches a specified regex pattern + * 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 { + String field = (String)getParameters().get("field"); + if (field == null) { + return false; + } + + String[] fieldParts = field.split("\\."); + String schema = (fieldParts.length > 0 ? fieldParts[0] : null); + String element = (fieldParts.length > 1 ? fieldParts[1] : null); + String qualifier = (fieldParts.length > 2 ? fieldParts[2] : null); + + List values = itemService.getMetadata(item, schema, element, qualifier, Item.ANY); + for (MetadataValue value : values) { + if (getParameters().get("pattern") instanceof String) { + String pattern = (String)getParameters().get("pattern"); + log.debug("logic for " + item.getHandle() + ": pattern passed is " + pattern + + ", checking value " + value.getValue()); + Pattern p = Pattern.compile(pattern); + Matcher m = p.matcher(value.getValue()); + if (m.find()) { + return true; + } + } + } + return false; + } +} 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 new file mode 100644 index 0000000000..894b5213e1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java @@ -0,0 +1,77 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A condition that returns true if any pattern in a list of patterns matches any value + * in a given metadata field + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class MetadataValuesMatchCondition extends AbstractCondition { + @Autowired(required = true) + protected ItemService itemService; + + private static Logger log = Logger.getLogger(MetadataValuesMatchCondition.class); + + /** + * Return true if any value for a specified field in the item matches any of the specified regex patterns + * 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 { + String field = (String)getParameters().get("field"); + if (field == null) { + return false; + } + + String[] fieldParts = field.split("\\."); + String schema = (fieldParts.length > 0 ? fieldParts[0] : null); + String element = (fieldParts.length > 1 ? fieldParts[1] : null); + String qualifier = (fieldParts.length > 2 ? fieldParts[2] : null); + + List values = itemService.getMetadata(item, schema, element, qualifier, Item.ANY); + for (MetadataValue value : values) { + if (getParameters().get("patterns") instanceof List) { + List patternList = (List)getParameters().get("patterns"); + // If the list is empty, just return true and log error? + log.error("No patterns were passed for metadata value matching, defaulting to 'true'"); + if (patternList == null) { + return true; + } + for (String pattern : patternList) { + log.debug("logic for " + item.getHandle() + ": pattern passed is " + pattern + + ", checking value " + value.getValue()); + Pattern p = Pattern.compile(pattern); + Matcher m = p.matcher(value.getValue()); + if (m.find()) { + return true; + } + } + } + } + return false; + } +} 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 new file mode 100644 index 0000000000..335cc03782 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.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.content.logic.condition; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.log4j.Logger; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A condition that accepts a group and action parameter and returns true if the group + * can perform the action on a given item + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class ReadableByGroupCondition extends AbstractCondition { + private static Logger log = Logger.getLogger(ReadableByGroupCondition.class); + + @Autowired(required = true) + AuthorizeService authorizeService; + + /** + * Return true if this item allows a specified action (eg READ, WRITE, ADD) by a specified group + * @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 { + + String group = (String)getParameters().get("group"); + String action = (String)getParameters().get("action"); + + try { + List policies = authorizeService + .getPoliciesActionFilter(context, item, Constants.getActionID(action)); + for (ResourcePolicy policy : policies) { + if (policy.getGroup().getName().equals(group)) { + return true; + } + } + } catch (SQLException e) { + log.error("Error trying to read policies for " + item.getHandle() + ": " + e.getMessage()); + throw new LogicalStatementException(e); + } + log.debug("item " + item.getHandle() + " not readable by anonymous group"); + + return false; + } +} 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 new file mode 100644 index 0000000000..826eb99a88 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.logic.operator; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * Abstract class for an operator. + * An operator contains a list of logical statements (conditions or more operators) and depending on the kind + * of operator (AND, OR, NOT, etc.) the results of some or all sub-statements are evaluated and returned + * as a logical result + * + * @author Kim Shepherd + * @version $Revision$ + */ +public abstract class AbstractOperator implements LogicalStatement { + + private List statements = new ArrayList<>(); + + /** + * Get sub-statements for this operator + * @return list of sub-statements + */ + public List getStatements() { + return statements; + } + + /** + * Set sub-statements for this operator, as defined in item-filters.xml + * @param statements list of logical statements + */ + public void setStatements(List statements) { + this.statements = statements; + } + + /** + * Default constructor + */ + public AbstractOperator() {} + + /** + * Constructor to create operator from some predefined statements + * @param statements + */ + public AbstractOperator(List statements) { + this.statements = statements; + } + + /** + * + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of evaluation (of sub-statements) + * @throws LogicalStatementException + */ + @Override + public Boolean getResult(Context context, Item item) throws LogicalStatementException { + return false; + } +} 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 new file mode 100644 index 0000000000..cdc4b8b86d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/And.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.content.logic.operator; + +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * An operator that implements AND by evaluating sub-statements and only returning + * true if all sub-statements return true + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class And extends AbstractOperator { + + /** + * Default constructor + */ + public And() { + super(); + } + + /** + * Constructor that accepts predefined list of statements as defined in item-filters.xml + * @param statements List of logical statements + */ + And(List statements) { + super(statements); + } + + /** + * Return true if ALL statements return true + * Return false otherwise + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of AND + * @throws LogicalStatementException + */ + @Override + public Boolean getResult(Context context, Item item) throws LogicalStatementException { + + for (LogicalStatement statement : getStatements()) { + if (!statement.getResult(context, item)) { + return false; + } + } + + return true; + } +} 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 new file mode 100644 index 0000000000..8d1cec727e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.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.content.logic.operator; + +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * An operator that implements NAND by negating an AND operation + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class Nand extends AbstractOperator { + + /** + * Default constructor + */ + public Nand() { + super(); + } + + /** + * Constructor that accepts predefined list of statements as defined in item-filters.xml + * @param statements List of logical statements + */ + public Nand(List statements) { + super(statements); + } + + /** + * Return true if the result of AND'ing all sub-statements is false (ie. a NOT(AND()) + * Return false if not + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of NAND + * @throws LogicalStatementException + */ + @Override + public Boolean getResult(Context context, Item item) throws LogicalStatementException { + return !(new And(getStatements()).getResult(context, item)); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java new file mode 100644 index 0000000000..bc2de9e502 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.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.content.logic.operator; + +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * An operator that implements NIR by negating an OR operation + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class Nor extends AbstractOperator { + + /** + * Default constructor + */ + public Nor() { + super(); + } + + /** + * Constructor that accepts predefined list of statements as defined in item-filters.xml + * @param statements List of logical statements + */ + public Nor(List statements) { + super(statements); + } + + /** + * Return true if the result of OR'ing the sub-statements is false + * Return false otherwise + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of NOR + * @throws LogicalStatementException + */ + @Override + public Boolean getResult(Context context, Item item) throws LogicalStatementException { + return !(new Or(getStatements()).getResult(context, item)); + } +} 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 new file mode 100644 index 0000000000..8d6a076c30 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java @@ -0,0 +1,69 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.logic.operator; + +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * An operator that implements NOT by simply negating a statement + * Note that this operator doesn't actually implement the 'AbstractOperator' interface because + * we only want one sub-statement. So it's actually just a simple implementation of LogicalStatement. + * 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 { + + private LogicalStatement statement; + + /** + * Get sub-statement (note: singular! even though we keep the method name) for this operator + * @return list of sub-statements + */ + public LogicalStatement getStatements() { + return statement; + } + + /** + * Set sub-statement (note: singular!) for this operator, as defined in item-filters.xml + * @param statement a single statement to apply to NOT operation + */ + public void setStatements(LogicalStatement statement) { + this.statement = statement; + } + + /** + * Default constructor + */ + public Not() {} + + /** + * Constructor that accepts predefined list of statements as defined in item-filters.xml + * @param statement Single logical statement + */ + public Not(LogicalStatement statement) { + this.statement = statement; + } + + /** + * Return true if the result of the sub-statement is false + * Return false otherwise + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of NOT + * @throws LogicalStatementException + */ + @Override + public Boolean getResult(Context context, Item item) throws LogicalStatementException { + return !statement.getResult(context, item); + } +} 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 new file mode 100644 index 0000000000..7943f09410 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.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.content.logic.operator; + +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * An operator that implements OR by evaluating sub-statements and returns + * true if one or more sub-statements return true + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class Or extends AbstractOperator { + + /** + * Default constructor + */ + public Or() { + super(); + } + + /** + * Constructor that accepts predefined list of statements as defined in item-filters.xml + * @param statements List of logical statements + */ + public Or(List statements) { + super(statements); + } + + /** + * Return true if any sub-statement returns true + * Return false otherwise + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of OR + * @throws LogicalStatementException + */ + @Override + public Boolean getResult(Context context, Item item) throws LogicalStatementException { + + for (LogicalStatement statement : getStatements()) { + if (statement.getResult(context, item)) { + return true; + } + } + + return false; + } +} 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 c8e9636bcb..8b5289f0c1 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -19,11 +19,14 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.LogicalStatementException; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.identifier.doi.DOIConnector; import org.dspace.identifier.doi.DOIIdentifierException; +import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; import org.dspace.identifier.service.DOIService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,8 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author Pascal-Nicolas Becker */ -public class DOIIdentifierProvider - extends IdentifierProvider { +public class DOIIdentifierProvider extends FilteredIdentifierProvider { private static final Logger log = LoggerFactory.getLogger(DOIIdentifierProvider.class); /** @@ -87,6 +89,11 @@ public class DOIIdentifierProvider @Autowired(required = true) protected ItemService itemService; + protected Filter filterService; + + /** + * Empty / default constructor for Spring + */ protected DOIIdentifierProvider() { } @@ -103,6 +110,10 @@ public class DOIIdentifierProvider */ private String NAMESPACE_SEPARATOR; + /** + * Get DOI prefix from configuration + * @return a String containing the DOI prefix + */ protected String getPrefix() { if (null == this.PREFIX) { this.PREFIX = this.configurationService.getProperty(CFG_PREFIX); @@ -116,6 +127,10 @@ public class DOIIdentifierProvider return this.PREFIX; } + /** + * Get namespace separator from configuration + * @return a String containing the namespace separator + */ protected String getNamespaceSeparator() { if (null == this.NAMESPACE_SEPARATOR) { this.NAMESPACE_SEPARATOR = this.configurationService.getProperty(CFG_NAMESPACE_SEPARATOR); @@ -126,11 +141,27 @@ public class DOIIdentifierProvider return this.NAMESPACE_SEPARATOR; } + /** + * Set the DOI connector, which is the component that commuincates with the remote registration service + * (eg. DataCite, EZID, Crossref) + * Spring will use this setter to set the DOI connector from the configured property in identifier-services.xml + * + * @param connector a DOIConnector + */ @Autowired(required = true) 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 + */ + public void setFilterService(Filter filterService) { + this.filterService = filterService; + } + /** * This identifier provider supports identifiers of type * {@link org.dspace.identifier.DOI}. @@ -164,23 +195,65 @@ public class DOIIdentifierProvider return true; } - + /** + * Register a new identifier for a given DSpaceObject, never skipping or ignoring any configured filter + * @param context - DSpace context + * @param dso - DSpaceObject to use for identifier registration + * @return identifier + * @throws IdentifierException + */ @Override public String register(Context context, DSpaceObject dso) + throws IdentifierException { + return register(context, dso, false); + } + + /** + * Register a specified DOI for a given DSpaceObject, never skipping or ignoring any configured filter + * @param context - DSpace context + * @param dso - DSpaceObject identified by the new DOI + * @param identifier - String containing the identifier to register + * @throws IdentifierException + */ + @Override + public void register(Context context, DSpaceObject dso, String identifier) + throws IdentifierException { + register(context, dso, identifier, false); + } + + /** + * 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 + * @throws IdentifierException + */ + @Override + public String register(Context context, DSpaceObject dso, Boolean skipFilter) throws IdentifierException { if (!(dso instanceof Item)) { // DOI are currently assigned only to Item return null; } - String doi = mint(context, dso); + + String doi = mint(context, dso, skipFilter); + // register tries to reserve doi if it's not already. // So we don't have to reserve it here. - register(context, dso, doi); + register(context, dso, doi, skipFilter); return doi; } + /** + * Register a specified DOI for a given DSpaceObject + * @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 + * @throws IdentifierException + */ @Override - public void register(Context context, DSpaceObject dso, String identifier) + public void register(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) throws IdentifierException { if (!(dso instanceof Item)) { // DOI are currently assigned only to Item @@ -191,7 +264,7 @@ public class DOIIdentifierProvider // search DOI in our db try { - doiRow = loadOrCreateDOI(context, dso, doi); + doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); } catch (SQLException ex) { log.error("Error in databse connection: " + ex.getMessage()); throw new RuntimeException("Error in database conncetion.", ex); @@ -200,7 +273,7 @@ public class DOIIdentifierProvider if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to register a DOI that " - + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); + + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); } // Check status of DOI @@ -234,6 +307,22 @@ public class DOIIdentifierProvider */ @Override public void reserve(Context context, DSpaceObject dso, String identifier) + throws IdentifierException, IllegalArgumentException { + reserve(context, dso, identifier, false); + } + + /** + * Reserve a specified DOI for a given DSpaceObject + * @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 + * @throws IdentifierException + * @throws IllegalArgumentException + * @throws SQLException + */ + @Override + public void reserve(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) throws IdentifierException, IllegalArgumentException { String doi = doiService.formatIdentifier(identifier); DOI doiRow = null; @@ -241,7 +330,7 @@ public class DOIIdentifierProvider 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); + doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); } catch (SQLException sqle) { throw new RuntimeException(sqle); } @@ -258,16 +347,39 @@ public class DOIIdentifierProvider } } + /** + * Perform the actual online / API interaction required to reserve the DOI online + * always applying filters if they are configured + * @param context - DSpace context + * @param dso - DSpaceObject identified by this DOI + * @param identifier - String containing the DOI to reserve + * @throws IdentifierException + * @throws IllegalArgumentException + * @throws SQLException + */ public void reserveOnline(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException, SQLException { + reserveOnline(context, dso, identifier, false); + } + + /** + * Perform the actual online / API interaction required to reserve the DOI online + * @param context - DSpace context + * @param dso - DSpaceObject identified by this DOI + * @param identifier - String containing the DOI to reserve + * @throws IdentifierException + * @throws IllegalArgumentException + * @throws SQLException + */ + public void reserveOnline(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + 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); + DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); - if (DELETED.equals(doiRow.getStatus()) || - TO_BE_DELETED.equals(doiRow.getStatus())) { + if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to reserve a DOI that " - + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); + + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); } connector.reserveDOI(context, dso, doi); @@ -276,16 +388,43 @@ public class DOIIdentifierProvider doiService.update(context, doiRow); } + /** + * Perform the actual online / API interaction required to register the DOI online + * always applying filters if they are configured + * @param context - DSpace context + * @param dso - DSpaceObject identified by this DOI + * @param identifier - String containing the DOI to register + * @throws IdentifierException + * @throws IllegalArgumentException + * @throws SQLException + */ public void registerOnline(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException, SQLException { + + registerOnline(context, dso, identifier, false); + + } + + /** + * Perform the actual online / API interaction required to register the DOI online + * @param context - DSpace context + * @param dso - DSpaceObject identified by this DOI + * @param identifier - String containing the DOI to register + * @throws IdentifierException + * @throws IllegalArgumentException + * @throws SQLException + */ + public void registerOnline(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + throws IdentifierException, IllegalArgumentException, SQLException { + log.debug("registerOnline: skipFilter is " + skipFilter.toString()); + String doi = doiService.formatIdentifier(identifier); // get TableRow and ensure DOI belongs to dso regarding our db - DOI doiRow = loadOrCreateDOI(context, dso, doi); + DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); - if (DELETED.equals(doiRow.getStatus()) || - TO_BE_DELETED.equals(doiRow.getStatus())) { + if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to register a DOI that " - + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); + + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); } // register DOI Online @@ -294,7 +433,7 @@ public class DOIIdentifierProvider } 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); + this.reserveOnline(context, dso, identifier, skipFilter); connector.registerDOI(context, dso, doi); } else { throw die; @@ -314,15 +453,37 @@ public class DOIIdentifierProvider doiService.update(context, doiRow); } + /** + * Update metadata for a registered object + * If the DOI for hte item already exists, *always* skip the filter since it should only be used for + * allowing / disallowing reservation and registration, not metadata updates or deletions + * + * @param context - DSpace context + * @param dso - DSpaceObject identified by this DOI + * @param identifier - String containing the DOI to reserve + * @throws IdentifierException + * @throws IllegalArgumentException + * @throws SQLException + */ public void updateMetadata(Context context, DSpaceObject dso, String identifier) - throws IdentifierException, IllegalArgumentException, SQLException { - String doi = doiService.formatIdentifier(identifier); - DOI doiRow = loadOrCreateDOI(context, dso, doi); + throws IdentifierException, IllegalArgumentException, SQLException { - if (DELETED.equals(doiRow.getStatus()) || - TO_BE_DELETED.equals(doiRow.getStatus())) { + String doi = doiService.formatIdentifier(identifier); + + Boolean skipFilter = false; + + 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; + } + + DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + + if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to register a DOI that " - + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); + + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); } if (IS_REGISTERED.equals(doiRow.getStatus())) { @@ -338,8 +499,20 @@ public class DOIIdentifierProvider doiService.update(context, doiRow); } + /** + * Update metadata for a registered object in the DOI Connector to update the agency records + * If the DOI for hte item already exists, *always* skip the filter since it should only be used for + * allowing / disallowing reservation and registration, not metadata updates or deletions + * + * @param context - DSpace context + * @param dso - DSpaceObject identified by this DOI + * @param identifier - String containing the DOI to reserve + * @throws IdentifierException + * @throws IllegalArgumentException + * @throws SQLException + */ public void updateMetadataOnline(Context context, DSpaceObject dso, String identifier) - throws IdentifierException, SQLException { + throws IdentifierException, SQLException { String doi = doiService.formatIdentifier(identifier); // ensure DOI belongs to dso regarding our db @@ -348,31 +521,28 @@ public class DOIIdentifierProvider doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); } catch (SQLException sqle) { log.warn("SQLException while searching a DOI in our db.", sqle); - throw new RuntimeException("Unable to retrieve information about " + - "a DOI out of database.", sqle); + throw new RuntimeException("Unable to retrieve information about a DOI out of database.", sqle); } if (null == doiRow) { - log.error("Cannot update metadata for DOI {}: unable to find it in " - + "our db.", doi); + log.error("Cannot update metadata for DOI {}: unable to find it in our db.", doi); throw new DOIIdentifierException("Unable to find DOI.", - DOIIdentifierException.DOI_DOES_NOT_EXIST); + DOIIdentifierException.DOI_DOES_NOT_EXIST); } if (!Objects.equals(doiRow.getDSpaceObject(), dso)) { log.error("Refuse to update metadata of DOI {} with the metadata of " - + " an object ({}/{}) the DOI is not dedicated to.", + + " an object ({}/{}) the DOI is not dedicated to.", doi, contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso), dso.getID().toString()); throw new DOIIdentifierException("Cannot update DOI metadata: " - + "DOI and DSpaceObject does not match!", - DOIIdentifierException.MISMATCH); + + "DOI and DSpaceObject does not match!", + DOIIdentifierException.MISMATCH); } - if (DELETED.equals(doiRow.getStatus()) || - TO_BE_DELETED.equals(doiRow.getStatus())) { + if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to update the metadata" - + "of a DOI that is marked as DELETED.", - DOIIdentifierException.DOI_IS_DELETED); + + "of a DOI that is marked as DELETED.", + DOIIdentifierException.DOI_IS_DELETED); } connector.updateMetadata(context, dso, doi); @@ -388,41 +558,69 @@ public class DOIIdentifierProvider doiService.update(context, doiRow); } + /** + * Mint a new DOI in DSpace - this is usually the first step of registration + * Always apply filters if they are configured + * @param context - DSpace context + * @param dso - DSpaceObject identified by the new identifier + * @return a String containing the new identifier + * @throws IdentifierException + */ @Override public String mint(Context context, DSpaceObject dso) - throws IdentifierException { + throws IdentifierException { + return mint(context, dso, false); + } + + /** + * 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 + * @return a String containing the new identifier + * @throws IdentifierException + */ + @Override + public String mint(Context context, DSpaceObject dso, Boolean skipFilter) throws IdentifierException { + String doi = null; try { doi = getDOIByObject(context, dso); } catch (SQLException e) { log.error("Error while attemping to retrieve information about a DOI for " - + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso - .getID() + "."); + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso.getID() + "."); throw new RuntimeException("Error while attempting to retrieve " + - "information about a DOI for " + contentServiceFactory - .getDSpaceObjectService(dso).getTypeText(dso) + - " with ID " + dso.getID() + ".", e); + "information about a DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + + " with ID " + dso.getID() + ".", e); } if (null == doi) { try { - DOI doiRow = loadOrCreateDOI(context, dso, null); + DOI doiRow = loadOrCreateDOI(context, dso, null, skipFilter); doi = DOI.SCHEME + doiRow.getDoi(); } catch (SQLException e) { log.error("Error while creating new DOI for Object of " + - "ResourceType {} with id {}.", dso.getType(), dso.getID()); + "ResourceType {} with id {}.", dso.getType(), dso.getID()); throw new RuntimeException("Error while attempting to create a " + - "new DOI for " + contentServiceFactory.getDSpaceObjectService(dso) - .getTypeText(dso) + " with ID " + - dso.getID() + ".", e); + "new DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + + dso.getID() + ".", e); } } return doi; } + /** + * Resolve an identifier to a DSpaceObject, if it is registered + * @param context - DSpace context + * @param identifier - to be resolved. + * @param attributes - additional information for resolving {@code identifier}. + * @return a DSpaceObject identified by the identifier string + * @throws IdentifierNotFoundException + * @throws IdentifierNotResolvableException + */ @Override public DSpaceObject resolve(Context context, String identifier, String... attributes) - throws IdentifierNotFoundException, IdentifierNotResolvableException { + throws IdentifierNotFoundException, IdentifierNotResolvableException { String doi = null; try { doi = doiService.formatIdentifier(identifier); @@ -437,16 +635,23 @@ public class DOIIdentifierProvider return dso; } catch (SQLException sqle) { log.error("SQLException while searching a DOI in our db.", sqle); - throw new RuntimeException("Unable to retrieve information about " + - "a DOI out of database.", sqle); + throw new RuntimeException("Unable to retrieve information about a DOI out of database.", sqle); } catch (IdentifierException e) { throw new IdentifierNotResolvableException(e); } } + /** + * Look up a DOI identifier for a given DSpaceObject + * @param context - DSpace context + * @param dso - DSpaceObject to look up + * @return a String containing the DOI + * @throws IdentifierNotFoundException + * @throws IdentifierNotResolvableException + */ @Override public String lookup(Context context, DSpaceObject dso) - throws IdentifierNotFoundException, IdentifierNotResolvableException { + throws IdentifierNotFoundException, IdentifierNotResolvableException { String doi = null; try { doi = getDOIByObject(context, dso); @@ -455,18 +660,23 @@ public class DOIIdentifierProvider } if (null == doi) { - throw new IdentifierNotFoundException("No DOI for DSpaceObject of type " - + contentServiceFactory.getDSpaceObjectService(dso) - .getTypeText(dso) + " with ID " + dso - .getID() + " found."); + throw new IdentifierNotFoundException("No DOI for DSpaceObject of type " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + + " with ID " + dso.getID() + " found."); } return doi; } + /** + * Delete all DOIs for a DSpaceObject + * @param context - DSpace context + * @param dso - DSpaceObject to have all its DOIs deleted + * @throws IdentifierException + */ @Override public void delete(Context context, DSpaceObject dso) - throws IdentifierException { + throws IdentifierException { // delete all DOIs for this Item from our database. try { String doi = getDOIByObject(context, dso); @@ -475,13 +685,12 @@ public class DOIIdentifierProvider doi = getDOIByObject(context, dso); } } catch (SQLException ex) { - log.error("Error while attemping to retrieve information about a DOI for " - + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso - .getID() + ".", ex); + log.error("Error while attemping to retrieve information about a DOI for " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + + " with ID " + dso.getID() + ".", ex); throw new RuntimeException("Error while attempting to retrieve " + - "information about a DOI for " + contentServiceFactory - .getDSpaceObjectService(dso).getTypeText(dso) + - " with ID " + dso.getID() + ".", ex); + "information about a DOI for " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + + " with ID " + dso.getID() + ".", ex); } // delete all DOIs of this item out of its metadata @@ -493,28 +702,33 @@ public class DOIIdentifierProvider doi = getDOIOutOfObject(dso); } } catch (AuthorizeException ex) { - log.error("Error while removing a DOI out of the metadata of an " - + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso - .getID() + ".", ex); - throw new RuntimeException("Error while removing a DOI out of the " - + "metadata of an " + contentServiceFactory.getDSpaceObjectService(dso) - .getTypeText(dso) + " with ID " - + dso.getID() + ".", ex); + log.error("Error while removing a DOI out of the metadata of an " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + + " with ID " + dso.getID() + ".", ex); + throw new RuntimeException("Error while removing a DOI out of the metadata of an " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + + " with ID " + dso.getID() + ".", ex); } catch (SQLException ex) { - log.error("Error while removing a DOI out of the metadata of an " - + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + " with ID " + dso - .getID() + ".", ex); - throw new RuntimeException("Error while removing a DOI out of the " - + "metadata of an " + contentServiceFactory.getDSpaceObjectService(dso) - .getTypeText(dso) + " with ID " - + dso.getID() + ".", ex); + log.error("Error while removing a DOI out of the metadata of an " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + + " with ID " + dso.getID() + ".", ex); + throw new RuntimeException("Error while removing a DOI out of the " + + "metadata of an " + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + + " with ID " + dso.getID() + ".", ex); } } + /** + * Delete a specific DOI for a given DSpaceObject + * @param context - DSpace context + * @param dso - DSpaceObject to be de-identified. + * @param identifier - String containing identifier to delete + * @throws IdentifierException + */ @Override public void delete(Context context, DSpaceObject dso, String identifier) - throws IdentifierException { + throws IdentifierException { String doi = doiService.formatIdentifier(identifier); DOI doiRow = null; @@ -528,8 +742,8 @@ public class DOIIdentifierProvider if (null != doiRow) { if (!Objects.equals(dso, doiRow.getDSpaceObject())) { throw new DOIIdentifierException("Trying to delete a DOI out of " - + "an object that is not addressed by the DOI.", - DOIIdentifierException.MISMATCH); + + "an object that is not addressed by the DOI.", + DOIIdentifierException.MISMATCH); } } @@ -539,12 +753,12 @@ public class DOIIdentifierProvider } catch (AuthorizeException ex) { log.error("Not authorized to delete a DOI out of an Item.", ex); throw new DOIIdentifierException("Not authorized to delete DOI.", - ex, DOIIdentifierException.UNAUTHORIZED_METADATA_MANIPULATION); + ex, DOIIdentifierException.UNAUTHORIZED_METADATA_MANIPULATION); } catch (SQLException ex) { log.error("SQLException occurred while deleting a DOI out of an item: " - + ex.getMessage()); + + ex.getMessage()); throw new RuntimeException("Error while deleting a DOI out of the " + - "metadata of an Item " + dso.getID(), ex); + "metadata of an Item " + dso.getID(), ex); } // change doi status in db if necessary. @@ -567,8 +781,13 @@ public class DOIIdentifierProvider // DOIS. But it is possible to mark a DOI as "inactive". } - public void deleteOnline(Context context, String identifier) - throws DOIIdentifierException { + /** + * Delete a specific DOI in the registration agency records via the DOI Connector + * @param context - DSpace context + * @param identifier - String containing identifier to delete + * @throws IdentifierException + */ + public void deleteOnline(Context context, String identifier) throws DOIIdentifierException { String doi = doiService.formatIdentifier(identifier); DOI doiRow = null; @@ -579,16 +798,15 @@ public class DOIIdentifierProvider } if (null == doiRow) { throw new DOIIdentifierException("This identifier: " + identifier - + " isn't in our database", - DOIIdentifierException.DOI_DOES_NOT_EXIST); + + " isn't in our database", + DOIIdentifierException.DOI_DOES_NOT_EXIST); } if (!TO_BE_DELETED.equals(doiRow.getStatus())) { - log.error("This identifier: {} couldn't be deleted. " - + "Delete it first from metadata.", - DOI.SCHEME + doiRow.getDoi()); + log.error("This identifier: {} couldn't be deleted. Delete it first from metadata.", + DOI.SCHEME + doiRow.getDoi()); throw new IllegalArgumentException("Couldn't delete this identifier:" - + DOI.SCHEME + doiRow.getDoi() - + ". Delete it first from metadata."); + + DOI.SCHEME + doiRow.getDoi() + + ". Delete it first from metadata."); } connector.deleteDOI(context, doi); @@ -603,17 +821,16 @@ public class DOIIdentifierProvider /** * Returns a DSpaceObject depending on its DOI. - * - * @param context The relevant DSpace Context. + * @param context the context * @param identifier The DOI in a format that is accepted by * {@link org.dspace.identifier.service.DOIService#formatIdentifier(String)}. * @return Null if the DOI couldn't be found or the associated DSpaceObject. - * @throws SQLException if database error - * @throws DOIIdentifierException If {@code identifier} is null or an empty string. + * @throws SQLException if database error + * @throws IdentifierException If {@code identifier} is null or an empty string. * @throws IllegalArgumentException If the identifier couldn't be recognized as DOI. */ public DSpaceObject getObjectByDOI(Context context, String identifier) - throws SQLException, DOIIdentifierException, IllegalArgumentException { + throws SQLException, DOIIdentifierException, IllegalArgumentException { String doi = doiService.formatIdentifier(identifier); DOI doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); @@ -622,10 +839,9 @@ public class DOIIdentifierProvider } if (doiRow.getDSpaceObject() == null) { - log.error("Found DOI " + doi + - " in database, but no assigned Object could be found."); + log.error("Found DOI " + doi + " in database, but no assigned Object could be found."); throw new IllegalStateException("Found DOI " + doi + - " in database, but no assigned Object could be found."); + " in database, but no assigned Object could be found."); } return doiRow.getDSpaceObject(); @@ -640,8 +856,7 @@ public class DOIIdentifierProvider * @return The DOI as String or null if DOI was not found. * @throws SQLException if database error */ - public String getDOIByObject(Context context, DSpaceObject dso) - throws SQLException { + public String getDOIByObject(Context context, DSpaceObject dso) throws SQLException { // String sql = "SELECT * FROM Doi WHERE resource_type_id = ? " + // "AND resource_id = ? AND ((status != ? AND status != ?) OR status IS NULL)"; @@ -651,13 +866,10 @@ public class DOIIdentifierProvider } if (doiRow.getDoi() == null) { - log.error("A DOI with an empty doi column was found in the database. DSO-Type: " - + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ", ID: " + dso - .getID() + "."); - throw new IllegalStateException("A DOI with an empty doi column " + - "was found in the database. DSO-Type: " + contentServiceFactory - .getDSpaceObjectService(dso).getTypeText(dso) + - ", ID: " + dso.getID() + "."); + log.error("A DOI with an empty doi column was found in the database. DSO-Type: " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ", ID: " + dso.getID() + "."); + throw new IllegalStateException("A DOI with an empty doi column was found in the database. DSO-Type: " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + ", ID: " + dso.getID() + "."); } return DOI.SCHEME + doiRow.getDoi(); @@ -679,8 +891,31 @@ public class DOIIdentifierProvider * DOI is registered for another object already. */ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier) + throws SQLException, DOIIdentifierException { + return loadOrCreateDOI(context, dso, doiIdentifier, false); + } + + /** + * Load DOI from database, or create one if it doesn't yet exist + * We need to distinguish several cases. LoadOrCreate can be called with a specifid identifier to load or create. + * It can also be used to create a new unspecified identifier. In the latter case doiIdentifier is set null. + * If doiIdentifier is set, we know which doi we should try to load or create, but even in sucha situation + * we might be able to find it in the database or might have to create it. + * + * @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 canMint() check + * @return + * @throws SQLException + * @throws DOIIdentifierException + */ + protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, Boolean skipFilter) throws SQLException, DOIIdentifierException { + DOI doi = null; + + // Was an identifier specified that we shall try to load or create if it is not existing yet? if (null != doiIdentifier) { // we expect DOIs to have the DOI-Scheme except inside the doi table: doiIdentifier = doiIdentifier.substring(DOI.SCHEME.length()); @@ -692,21 +927,22 @@ public class DOIIdentifierProvider // doi was deleted, check resource type if (doi.getResourceTypeId() != null && doi.getResourceTypeId() != dso.getType()) { - // doi was assigend to another resource type. Don't + // doi was assigned to another resource type. Don't // reactivate it throw new DOIIdentifierException("Cannot reassing " - + "previously deleted DOI " + doiIdentifier - + " as the resource types of the object it was " - + "previously assigned to and the object it " - + "shall be assigned to now divert (was: " - + Constants.typeText[doi.getResourceTypeId()] - + ", trying to assign to " - + Constants.typeText[dso.getType()] + ").", - DOIIdentifierException.DOI_IS_DELETED); + + "previously deleted DOI " + doiIdentifier + + " as the resource types of the object it was " + + "previously assigned to and the object it " + + "shall be assigned to now divert (was: " + + Constants.typeText[doi.getResourceTypeId()] + + ", trying to assign to " + + Constants.typeText[dso.getType()] + ").", + DOIIdentifierException.DOI_IS_DELETED); } else { // reassign doi // nothing to do here, doi will br reassigned after this // if-else-if-else-...-block + // will check if a filter prohibits creation of DOIs after this if-else-block } } else { // doi is assigned to a DSO; is it assigned to our specific dso? @@ -715,24 +951,47 @@ public class DOIIdentifierProvider return doi; } else { throw new DOIIdentifierException("Trying to create a DOI " + - "that is already reserved for another object.", - DOIIdentifierException.DOI_ALREADY_EXISTS); + "that is already reserved for another object.", + DOIIdentifierException.DOI_ALREADY_EXISTS); } } } + // 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 + boolean canMintDOI = canMint(context, dso); + log.debug("Called canMint(), result was " + canMintDOI + + " (and presumably an exception was not thrown)"); + } + // check prefix if (!doiIdentifier.startsWith(this.getPrefix() + "/")) { throw new DOIIdentifierException("Trying to create a DOI " + - "that's not part of our Namespace!", - DOIIdentifierException.FOREIGN_DOI); + "that's not part of our Namespace!", + DOIIdentifierException.FOREIGN_DOI); } if (doi == null) { // prepare new doiRow doi = doiService.create(context); } } else { - // We need to generate a new DOI. + // 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 prohibeted by a filter + boolean canMintDOI = canMint(context, dso); + log.debug("Called canMint(), result was " + canMintDOI + + " (and presumably an exception was not thrown)"); + } + doi = doiService.create(context); doiIdentifier = this.getPrefix() + "/" + this.getNamespaceSeparator() + doi.getID(); @@ -745,7 +1004,7 @@ public class DOIIdentifierProvider try { doiService.update(context, doi); } catch (SQLException e) { - throw new RuntimeException("Cannot save DOI to databse for unkown reason."); + throw new RuntimeException("Cannot save DOI to database for unknown reason."); } return doi; @@ -758,13 +1017,11 @@ public class DOIIdentifierProvider * @return The DOI or null if no DOI was found. * @throws DOIIdentifierException if identifier error */ - public String getDOIOutOfObject(DSpaceObject dso) - throws DOIIdentifierException { + public String getDOIOutOfObject(DSpaceObject dso) throws DOIIdentifierException { // FIXME if (!(dso instanceof Item)) { - throw new IllegalArgumentException("We currently support DOIs for " - + "Items only, not for " + contentServiceFactory - .getDSpaceObjectService(dso).getTypeText(dso) + "."); + throw new IllegalArgumentException("We currently support DOIs for Items only, not for " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + "."); } Item item = (Item) dso; @@ -789,17 +1046,16 @@ public class DOIIdentifierProvider * @throws IdentifierException if identifier error */ protected void saveDOIToObject(Context context, DSpaceObject dso, String doi) - throws SQLException, AuthorizeException, IdentifierException { + throws SQLException, AuthorizeException, IdentifierException { // FIXME if (!(dso instanceof Item)) { - throw new IllegalArgumentException("We currently support DOIs for " - + "Items only, not for " + contentServiceFactory - .getDSpaceObjectService(dso).getTypeText(dso) + "."); + throw new IllegalArgumentException("We currently support DOIs for Items only, not for " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + "."); } Item item = (Item) dso; - itemService - .addMetadata(context, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, doiService.DOIToExternalForm(doi)); + itemService.addMetadata(context, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, + doiService.DOIToExternalForm(doi)); try { itemService.update(context, item); } catch (SQLException | AuthorizeException ex) { @@ -821,9 +1077,8 @@ public class DOIIdentifierProvider throws AuthorizeException, SQLException, IdentifierException { // FIXME if (!(dso instanceof Item)) { - throw new IllegalArgumentException("We currently support DOIs for " - + "Items only, not for " + contentServiceFactory - .getDSpaceObjectService(dso).getTypeText(dso) + "."); + throw new IllegalArgumentException("We currently support DOIs for Items only, not for " + + contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso) + "."); } Item item = (Item) dso; @@ -838,7 +1093,39 @@ public class DOIIdentifierProvider itemService.clearMetadata(context, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null); itemService.addMetadata(context, item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, - remainder); + remainder); itemService.update(context, item); } -} + + /** + * 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 + * @return + * @throws DOIIdentifierNotApplicableException + */ + @Override + public Boolean canMint(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException { + // Default is 'true' in the case of a null/missing filter. All we really care about is whether + // an exception was thrown or not. + log.debug("canMint is being called"); + if (this.filterService != null && contentServiceFactory + .getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) { + try { + Boolean result = filterService.getResult(context, (Item) dso); + log.debug("Result of filter for " + dso.getHandle() + " is " + result.toString()); + if (!result) { + throw new DOIIdentifierNotApplicableException("Item " + dso.getHandle() + + " was evaluated as 'false' by the item filter, not minting"); + } + } catch (LogicalStatementException e) { + log.error("Error evaluating item with logical filter: " + e.getLocalizedMessage()); + throw new DOIIdentifierNotApplicableException(e); + } + } else { + log.debug("DOI Identifier Provider: filterService is null"); + } + + return true; + } +} \ 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 new file mode 100644 index 0000000000..a92475529f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.identifier; + +import java.sql.SQLException; + +import org.dspace.content.DSpaceObject; +import org.dspace.content.logic.Filter; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This abstract class adds extra method signatures so that implementing IdentifierProviders can + * handle "skip filter" booleans, so that any configured filters can be skipped and DOI registration forced. + * + * @author Kim Shepherd + * @version $Revision$ + */ +public abstract class FilteredIdentifierProvider extends IdentifierProvider { + + protected Filter filterService; + + /** + * 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 + */ + @Autowired + public void setFilterService(Filter filterService) { + this.filterService = filterService; + } + + /** + * 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 + * @return identifier + * @throws IdentifierException + */ + public abstract String register(Context context, DSpaceObject dso, Boolean skipFilter) + throws IdentifierException; + + /** + * Register a specified identifier for a given DSpaceObject + * @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 + * @throws IdentifierException + */ + public abstract void register(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + throws IdentifierException; + + /** + * Reserve a specified identifier for a given DSpaceObject (eg. reserving a DOI online with a registration agency) + * @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 + * @throws IdentifierException + * @throws IllegalArgumentException + * @throws SQLException + */ + public abstract void reserve(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + 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 + * @return a String containing the new identifier + * @throws IdentifierException + */ + public abstract String mint(Context context, DSpaceObject dso, Boolean skipFilter) throws IdentifierException; + + /** + * Check configured item filters to see if this identifier is allowed to be minted + * @param context - DSpace context + * @param dso - DSpaceObject to be inspected + * @return a Boolean indicating whether an identifier may be minted for this item (after filters are applied) + * @throws IdentifierException + */ + public abstract Boolean canMint(Context context, 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 fd68bce882..4c889fac89 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -19,6 +19,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; +import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; import org.dspace.identifier.service.IdentifierService; import org.springframework.beans.factory.annotation.Autowired; @@ -66,9 +67,13 @@ public class IdentifierServiceImpl implements IdentifierService { public void reserve(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException { for (IdentifierProvider service : providers) { - String identifier = service.mint(context, dso); - if (!StringUtils.isEmpty(identifier)) { - service.reserve(context, dso, identifier); + try { + String identifier = service.mint(context, dso); + if (!StringUtils.isEmpty(identifier)) { + service.reserve(context, dso, identifier); + } + } catch (DOIIdentifierNotApplicableException e) { + log.warn("DOI Identifier not reserved (inapplicable): " + e.getMessage()); } } //Update our item @@ -81,7 +86,11 @@ public class IdentifierServiceImpl implements IdentifierService { // Next resolve all other services for (IdentifierProvider service : providers) { if (service.supports(identifier)) { - service.reserve(context, dso, identifier); + try { + service.reserve(context, dso, identifier); + } catch (DOIIdentifierNotApplicableException e) { + log.warn("DOI Identifier not reserved (inapplicable): " + e.getMessage()); + } } } //Update our item @@ -94,7 +103,11 @@ public class IdentifierServiceImpl implements IdentifierService { //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) { - service.register(context, dso); + try { + service.register(context, dso); + } catch (DOIIdentifierNotApplicableException e) { + log.warn("DOI Identifier not registered (inapplicable): " + e.getMessage()); + } } //Update our item / collection / community contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); @@ -108,8 +121,12 @@ public class IdentifierServiceImpl implements IdentifierService { boolean registered = false; for (IdentifierProvider service : providers) { if (service.supports(identifier)) { - service.register(context, object, identifier); - registered = true; + try { + service.register(context, object, identifier); + registered = true; + } catch (DOIIdentifierNotApplicableException e) { + log.warn("DOI Identifier not registered (inapplicable): " + e.getMessage()); + } } } if (!registered) { @@ -152,8 +169,7 @@ public class IdentifierServiceImpl implements IdentifierService { if (!StringUtils.isEmpty(result)) { if (log.isDebugEnabled()) { try { - log.debug("Got an identifier from " - + service.getClass().getCanonicalName() + "."); + log.debug("Got an identifier from " + service.getClass().getCanonicalName() + "."); } catch (NullPointerException ex) { log.debug(ex.getMessage(), ex); } @@ -176,9 +192,9 @@ public class IdentifierServiceImpl implements IdentifierService { String handle = dso.getHandle(); if (!StringUtils.isEmpty(handle)) { if (!identifiers.contains(handle) - && !identifiers.contains("hdl:" + handle) - && !identifiers.contains(handleService.getCanonicalForm(handle))) { - // The VerionedHandleIdentifierProvider gets loaded by default + && !identifiers.contains("hdl:" + handle) + && !identifiers.contains(handleService.getCanonicalForm(handle))) { + // The VersionedHandleIdentifierProvider gets loaded by default // it returns handles without any scheme (neither hdl: nor http:). // If the VersionedHandleIdentifierProvider is not loaded, // we adds the handle in way it would. @@ -224,8 +240,7 @@ public class IdentifierServiceImpl implements IdentifierService { } @Override - public void delete(Context context, DSpaceObject dso) - throws AuthorizeException, SQLException, IdentifierException { + public void delete(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException { for (IdentifierProvider service : providers) { try { service.delete(context, dso); diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java new file mode 100644 index 0000000000..a320baa806 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java @@ -0,0 +1,27 @@ +package org.dspace.identifier.doi; + +/** + * + * Thrown when an identifier should not be applied to an item, eg. when it has been filtered by an item filter + * + * + * @author Kim Shepherd + */ +public class DOIIdentifierNotApplicableException extends DOIIdentifierException { + + public DOIIdentifierNotApplicableException() { + super(); + } + + public DOIIdentifierNotApplicableException(String message) { + super(message); + } + + public DOIIdentifierNotApplicableException(String message, Throwable cause) { + super(message, cause); + } + + public DOIIdentifierNotApplicableException(Throwable cause) { + super(cause); + } +} 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 afcac9cebe..2ce7401a28 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 @@ -61,7 +61,13 @@ public class DOIOrganiser { protected ItemService itemService; protected DOIService doiService; protected ConfigurationService configurationService; + protected Boolean skipFilter; + /** + * Constructor to be called within the main() method + * @param context - DSpace context + * @param provider - DOI identifier provider to use + */ public DOIOrganiser(Context context, DOIIdentifierProvider provider) { this.context = context; this.provider = provider; @@ -70,8 +76,13 @@ public class DOIOrganiser { this.itemService = ContentServiceFactory.getInstance().getItemService(); this.doiService = IdentifierServiceFactory.getInstance().getDOIService(); this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + this.skipFilter = false; } + /** + * Main command-line runner method as with other DSpace launcher commands + * @param args - the command line arguments to parse as parameters + */ public static void main(String[] args) { LOG.debug("Starting DOI organiser "); @@ -82,8 +93,7 @@ public class DOIOrganiser { context.turnOffAuthorisationSystem(); DOIOrganiser organiser = new DOIOrganiser(context, - new DSpace().getSingletonService(DOIIdentifierProvider.class)); - + new DSpace().getSingletonService(DOIIdentifierProvider.class)); // run command line interface runCLI(context, organiser, args); @@ -97,7 +107,7 @@ public class DOIOrganiser { } public static void runCLI(Context context, DOIOrganiser organiser, String[] args) { - // initlize options + // initialize options Options options = new Options(); options.addOption("h", "help", false, "Help"); @@ -115,6 +125,9 @@ public class DOIOrganiser { 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 registerDoi = Option.builder() .longOpt("register-doi") .hasArg() @@ -157,7 +170,6 @@ public class DOIOrganiser { options.addOption(delete); - // initialize parser CommandLineParser parser = new DefaultParser(); CommandLine line = null; @@ -170,7 +182,6 @@ public class DOIOrganiser { System.exit(1); } - // process options // user asks for help if (line.hasOption('h') || 0 == line.getOptions().length) { @@ -192,9 +203,13 @@ public class DOIOrganiser { } 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; + } if (line.hasOption('s')) { - try { List dois = doiService .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_RESERVED)); @@ -214,7 +229,6 @@ public class DOIOrganiser { } if (line.hasOption('r')) { - try { List dois = doiService .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_REGISTERED)); @@ -229,11 +243,14 @@ public class DOIOrganiser { } catch (SQLException ex) { System.err.println("Error in database connection:" + ex.getMessage()); ex.printStackTrace(System.err); + } catch (DOIIdentifierNotApplicableException e) { + System.err.println("DOI not registered: " + e.getMessage()); + } catch (DOIIdentifierException ex) { + System.err.println("Error registering DOI identifier:" + ex.getMessage()); } } if (line.hasOption('u')) { - try { List dois = doiService.getDOIsByStatus(context, Arrays.asList( DOIIdentifierProvider.UPDATE_BEFORE_REGISTRATION, @@ -255,7 +272,6 @@ public class DOIOrganiser { } if (line.hasOption('d')) { - try { List dois = doiService .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_DELETED)); @@ -277,7 +293,6 @@ public class DOIOrganiser { } } - if (line.hasOption("reserve-doi")) { String identifier = line.getOptionValue("reserve-doi"); @@ -339,7 +354,14 @@ public class DOIOrganiser { } - public void list(String processName, PrintStream out, PrintStream err, Integer... status) { + /** + * list DOIs queued for reservation or registration + * @param processName - process name for display + * @param out - output stream (eg. STDOUT) + * @param err - error output stream (eg. STDERR) + * @param status - status codes + */ + public void list(String processName, PrintStream out, PrintStream err, Integer ... status) { String indent = " "; if (null == out) { out = System.out; @@ -371,7 +393,14 @@ public class DOIOrganiser { } } - public void register(DOI doiRow) throws SQLException { + /** + * Register DOI with the provider + * @param doiRow - doi to register + * @param skipFilter - whether filters should be skipped before registration + * @throws SQLException + * @throws DOIIdentifierException + */ + public void register(DOI doiRow, Boolean skipFilter) throws SQLException, DOIIdentifierException { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only."); @@ -438,20 +467,49 @@ public class DOIOrganiser { } } - public void reserve(DOI doiRow) throws SQLException { + /** + * Register DOI with the provider, always applying (ie. never skipping) any configured filters + * @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); + } + + /** + * Reserve DOI with the provider, always applying (ie. never skipping) any configured filters + * @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 DOI with the provider + * @param doiRow - doi to reserve + * @throws SQLException + * @throws DOIIdentifierException + */ + public void reserve(DOI doiRow, Boolean skipFilter) { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { - throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only."); + throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only."); } try { - provider.reserveOnline(context, dso, - DOI.SCHEME + doiRow.getDoi()); + provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), skipFilter); if (!quiet) { - System.out.println("This identifier : " - + DOI.SCHEME + doiRow.getDoi() - + " is successfully reserved."); + System.out.println("This identifier : " + DOI.SCHEME + doiRow.getDoi() + " is successfully reserved."); } } catch (IdentifierException ex) { if (!(ex instanceof DOIIdentifierException)) { @@ -477,16 +535,14 @@ public class DOIOrganiser { .codeToString(doiIdentifierException.getCode()), ex); if (!quiet) { - System.err.println("It wasn't possible to reserve this identifier: " - + DOI.SCHEME + doiRow.getDoi()); + System.err.println("It wasn't possible to reserve this identifier: " + DOI.SCHEME + doiRow.getDoi()); } } catch (IllegalArgumentException ex) { LOG.error("Database table DOI contains a DOI that is not valid: " + DOI.SCHEME + doiRow.getDoi() + "!", ex); if (!quiet) { - System.err.println("It wasn't possible to reserve this identifier: " - + DOI.SCHEME + doiRow.getDoi()); + System.err.println("It wasn't possible to reserve this identifier: " + DOI.SCHEME + doiRow.getDoi()); } throw new IllegalStateException("Database table DOI contains a DOI " + " that is not valid: " @@ -495,19 +551,21 @@ public class DOIOrganiser { LOG.error("Error while trying to get data from database", ex); if (!quiet) { - System.err.println("It wasn't possible to reserve this identifier: " - + DOI.SCHEME + doiRow.getDoi()); + System.err.println("It wasn't possible to reserve this identifier: " + DOI.SCHEME + doiRow.getDoi()); } throw new RuntimeException("Error while trying to get data from database", ex); } } + /** + * Update metadata for a DOI + * @param doiRow - DOI to update + */ public void update(DOI doiRow) { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { - throw new IllegalArgumentException("Currenty DSpace supports DOIs " - + "for Items only."); + throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only."); } try { @@ -541,8 +599,7 @@ public class DOIOrganiser { .codeToString(doiIdentifierException.getCode()), ex); if (!quiet) { - System.err.println("It wasn't possible to update this identifier: " - + DOI.SCHEME + doiRow.getDoi()); + System.err.println("It wasn't possible to update this identifier: " + DOI.SCHEME + doiRow.getDoi()); } } catch (IllegalArgumentException ex) { @@ -550,8 +607,7 @@ public class DOIOrganiser { + DOI.SCHEME + doiRow.getDoi() + "!", ex); if (!quiet) { - System.err.println("It wasn't possible to update this identifier: " - + DOI.SCHEME + doiRow.getDoi()); + System.err.println("It wasn't possible to update this identifier: " + DOI.SCHEME + doiRow.getDoi()); } throw new IllegalStateException("Database table DOI contains a DOI " @@ -562,8 +618,12 @@ public class DOIOrganiser { } } - public void delete(String identifier) - throws SQLException { + /** + * Delete a DOI + * @param identifier - DOI to delete + * @throws SQLException + */ + public void delete(String identifier) throws SQLException { String doi = null; DOI doiRow = null; @@ -575,8 +635,7 @@ public class DOIOrganiser { doi.substring(DOI.SCHEME.length())); if (null == doiRow) { - throw new IllegalStateException("You specified a valid DOI," - + " that is not stored in our database."); + throw new IllegalStateException("You specified a valid DOI, that is not stored in our database."); } provider.deleteOnline(context, doi); @@ -642,15 +701,14 @@ public class DOIOrganiser { //Check if this Item has an Identifier, mint one if it doesn't if (null == doiRow) { - doi = provider.mint(context, dso); + doi = provider.mint(context, dso, this.skipFilter); doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); return doiRow; } return doiRow; } else { - throw new IllegalStateException("You specified an ItemID, " - + "that is not stored in our database."); + throw new IllegalStateException("You specified an ItemID, that is not stored in our database."); } } @@ -667,7 +725,7 @@ public class DOIOrganiser { doiRow = doiService.findDOIByDSpaceObject(context, dso); if (null == doiRow) { - doi = provider.mint(context, dso); + doi = provider.mint(context, dso, this.skipFilter); doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); } @@ -680,8 +738,7 @@ public class DOIOrganiser { doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); if (null == doiRow) { - throw new IllegalStateException("You specified a valid DOI," - + " that is not stored in our database."); + throw new IllegalStateException("You specified a valid DOI, that is not stored in our database."); } } catch (DOIIdentifierException ex) { // Identifier was not recognized as DOI. @@ -699,6 +756,14 @@ public class DOIOrganiser { return doiRow; } + /** + * Send an alert email to the configured recipient when DOI operations encounter an error + * @param action - action being attempted (eg. reserve, register, update) + * @param dso - DSpaceObject associated with the DOI + * @param doi - DOI for this operation + * @param reason - failure reason or error message + * @throws IOException + */ private void sendAlertMail(String action, DSpaceObject dso, String doi, String reason) throws IOException { String recipient = configurationService.getProperty("alert.recipient"); @@ -728,8 +793,11 @@ public class DOIOrganiser { } } + /** + * Set this runner to be in quiet mode, suppressing console output + */ private void setQuiet() { this.quiet = true; } -} +} \ No newline at end of file From 11d7b5b063536aa1361305eddbbe1097f6323ebf Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 3 Jun 2020 11:30:29 +1200 Subject: [PATCH 0014/1254] [DS-4522](main) RegisterDOI curation task (as CLI and UI control) --- .../org/dspace/ctask/general/RegisterDOI.java | 148 ++++++++++++++++++ dspace/config/modules/doi-curation.cfg | 6 + 2 files changed, 154 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java create mode 100644 dspace/config/modules/doi-curation.cfg 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 new file mode 100644 index 0000000000..afb853ccd4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java @@ -0,0 +1,148 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.ctask.general; + +import java.io.IOException; +import java.sql.SQLException; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.curate.AbstractCurationTask; +import org.dspace.curate.Curator; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; +import org.dspace.utils.DSpace; + +/** + * This curation task will register a DOI for an item, optionally ignoring any logical filtering applied + * to normal identifier registration and DOI service operation. + * + * @author Kim Shepherd + */ +public class RegisterDOI extends AbstractCurationTask { + // Curation task status + private int status = Curator.CURATE_UNSET; + // The skipFilter boolean has a default value of 'true', as per intended operation + private boolean skipFilter = true; + // The distributed boolean has a default value of 'false' for safest operation + private boolean distributed = false; + // Prefix for configuration module + private static final String PLUGIN_PREFIX = "doicuration"; + // Logger + private static final Logger log = LogManager.getLogger(RegisterDOI.class); + // DOI provider + private DOIIdentifierProvider provider; + + /** + * Initialise the curation task and read configuration, instantiate the DOI provider + */ + @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); + // Instantiate DOI provider singleton + provider = new DSpace().getSingletonService(DOIIdentifierProvider.class); + } + + /** + * Override the abstract 'perform' method to either distribute, or perform single-item + * depending on configuration. By default, the task is *not* distributed, since that could be unsafe + * and the original purpose of this task is to essentially implement a "Register DOI" button on the Edit Item page. + * @param dso DSpaceObject for which to register a DOI (must be item) + * @return status indicator + * @throws IOException + */ + @Override + public int perform(DSpaceObject dso) throws IOException { + // Check distribution configuration + if (distributed) { + // This task is configured for distributed use. Call distribute() and let performItem handle + // the main processing. + distribute(dso); + } else { + // This task is NOT configured for distributed use (default). Instead process a single item directly + if (dso instanceof Item) { + Item item = (Item) dso; + performRegistration(item); + } else { + log.warn("DOI registration attempted on non-item DSpace Object: " + dso.getID()); + } + return status; + } + return status; + } + + /** + * This is called when the task is distributed (ie. called on a set of items or over a whole structure) + * @param item the DSpace Item + */ + @Override + protected void performItem(Item item) { + performRegistration(item); + } + + /** + * Shared 'perform' code between perform() and performItem() - a curation wrapper for the register() method + * @param item the item for which to register a DOI + */ + private void performRegistration(Item item) { + // Request DOI registration and report results + String doi = register(item); + String result = "DOI registration task performed on " + item.getHandle() + "."; + if (doi != null) { + result += " DOI: (" + doi + ")"; + } else { + result += " DOI was null, either item was filtered or an error was encountered."; + } + setResult(result); + report(result); + } + + /** + * Perform the DOIIdentifierProvider.register call, with skipFilter passed as per config and defaults + * @param item The item for which to register a DOI + */ + private String register(Item item) { + String doi = null; + // Attempt DOI registration and report successes and failures + try { + doi = provider.register(Curator.curationContext(), item, skipFilter); + 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"; + report(message); + } else { + log.error("Got a null DOI after registering..."); + } + } catch (SQLException e) { + // Exception obtaining context + log.error("Error obtaining curator context: " + e.getMessage()); + status = Curator.CURATE_ERROR; + } catch (DOIIdentifierNotApplicableException e) { + // Filter returned 'false' so DOI was not registered. This is normal behaviour when filter is running. + log.info("Item was filtered from DOI registration: " + e.getMessage()); + String message = "Item " + item.getHandle() + " was skipped from DOI registration because it matched " + + "the item filter configured in identifier-services.xml."; + report(message); + status = Curator.CURATE_SUCCESS; + } catch (IdentifierException e) { + // Any other identifier exception is probably a true error + log.error("Error registering identifier: " + e.getMessage()); + status = Curator.CURATE_ERROR; + } + + return doi; + } + +} diff --git a/dspace/config/modules/doi-curation.cfg b/dspace/config/modules/doi-curation.cfg new file mode 100644 index 0000000000..620ae8d1f2 --- /dev/null +++ b/dspace/config/modules/doi-curation.cfg @@ -0,0 +1,6 @@ +### DOI registration curation task configuration module + +## +# Should any logical filters be skipped when registering DOIs? (ie. *always* register, never filter out the item) +# Default: true +#doicuration.skip-filter = true \ No newline at end of file From b049e6511019600a256073d6d73c04ad32633226 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 3 Jun 2020 12:28:06 +1200 Subject: [PATCH 0015/1254] [DS-4522](main) Configuration (spring), providers and filters --- .../org/dspace/ctask/general/RegisterDOI.java | 12 +- dspace/config/dspace.cfg | 2 + dspace/config/modules/curate.cfg | 1 + dspace/config/modules/doi-curation.cfg | 8 +- .../config/spring/api/identifier-service.xml | 15 + dspace/config/spring/api/item-filters.xml | 339 ++++++++++++++++++ 6 files changed, 371 insertions(+), 6 deletions(-) create mode 100644 dspace/config/spring/api/item-filters.xml 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 afb853ccd4..4e777d70a8 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 @@ -10,8 +10,7 @@ package org.dspace.ctask.general; import java.io.IOException; import java.sql.SQLException; -import org.apache.log4j.LogManager; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.curate.AbstractCurationTask; @@ -29,15 +28,15 @@ import org.dspace.utils.DSpace; */ public class RegisterDOI extends AbstractCurationTask { // Curation task status - private int status = Curator.CURATE_UNSET; + private int status = Curator.CURATE_SUCCESS; // The skipFilter boolean has a default value of 'true', as per intended operation private boolean skipFilter = true; // The distributed boolean has a default value of 'false' for safest operation private boolean distributed = false; // Prefix for configuration module - private static final String PLUGIN_PREFIX = "doicuration"; + private static final String PLUGIN_PREFIX = "doi-curation"; // Logger - private static final Logger log = LogManager.getLogger(RegisterDOI.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegisterDOI.class); // DOI provider private DOIIdentifierProvider provider; @@ -51,6 +50,8 @@ public class RegisterDOI extends AbstractCurationTask { 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 + + ", distributed = " + distributed); // Instantiate DOI provider singleton provider = new DSpace().getSingletonService(DOIIdentifierProvider.class); } @@ -117,6 +118,7 @@ public class RegisterDOI extends AbstractCurationTask { 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); if (doi != null) { String message = "New DOI minted in database for item " + item.getHandle() + ": " + doi diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index e9ca99957e..087a7a0800 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1524,6 +1524,8 @@ include = ${module_dir}/citation-page.cfg include = ${module_dir}/clamav.cfg include = ${module_dir}/curate.cfg include = ${module_dir}/discovery.cfg +include = ${module_dir}/doi-curation.cfg +include = ${module_dir}/fetchccdata.cfg include = ${module_dir}/google-analytics.cfg include = ${module_dir}/healthcheck.cfg include = ${module_dir}/irus-statistics.cfg diff --git a/dspace/config/modules/curate.cfg b/dspace/config/modules/curate.cfg index 2554a28750..b146bc6c2f 100644 --- a/dspace/config/modules/curate.cfg +++ b/dspace/config/modules/curate.cfg @@ -14,6 +14,7 @@ plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RequiredM #plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ClamScan = vscan #plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MicrosoftTranslator = translate plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MetadataValueLinkChecker = checklinks +plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RegisterDOI = registerdoi # add new tasks here (or in additional config files) ## task queue implementation diff --git a/dspace/config/modules/doi-curation.cfg b/dspace/config/modules/doi-curation.cfg index 620ae8d1f2..c9449b6520 100644 --- a/dspace/config/modules/doi-curation.cfg +++ b/dspace/config/modules/doi-curation.cfg @@ -3,4 +3,10 @@ ## # Should any logical filters be skipped when registering DOIs? (ie. *always* register, never filter out the item) # Default: true -#doicuration.skip-filter = true \ No newline at end of file +doi-curation.skip-filter = false + +## +# Should we allow the curation task to be distributed over communities / collections of items or the whole repository? +# This *could* be dangerous if run accidentally over more items than intended. +# Default: false +#doi-curation.distributed = false \ 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 d9a500ec36..e9f08003bd 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -67,6 +67,21 @@ ref="org.dspace.identifier.doi.DOIConnector" /> --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + article$ + bachelorThesis$ + masterThesis$ + doctoralThesis$ + book$ + bookPart$ + review$ + conferenceObject$ + lecture$ + workingPaper$ + preprint$ + report$ + annotation$ + contributionToPeriodical$ + patent$ + dataset$ + other$ + + + + + + + + + + + + + 123456789/20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 123456789/3 + 123456789/4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 677623a1b74e3fc5b415bccb0b54c304bcc908f3 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 4 Jun 2020 15:02:14 +1200 Subject: [PATCH 0016/1254] [DS-4522](main) Unit tests for filters and DOI provider --- .../content/logic/LogicalFilterTest.java | 358 ++++++++++++++++++ .../identifier/DOIIdentifierProviderTest.java | 87 ++++- 2 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java diff --git a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java new file mode 100644 index 0000000000..c0e8b84d50 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java @@ -0,0 +1,358 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.Logger; +import org.dspace.AbstractUnitTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataSchemaEnum; +import org.dspace.content.MetadataValue; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.condition.MetadataValueMatchCondition; +import org.dspace.content.logic.operator.And; +import org.dspace.content.logic.operator.Nand; +import org.dspace.content.logic.operator.Nor; +import org.dspace.content.logic.operator.Not; +import org.dspace.content.logic.operator.Or; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.dspace.content.service.InstallItemService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.content.service.MetadataValueService; +import org.dspace.content.service.WorkspaceItemService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for logical filters, conditions and operators + * @author Kim Shepherd + */ +public class LogicalFilterTest extends AbstractUnitTest { + // Required services + protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + private MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); + private MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); + + // Logger + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LogicalFilterTest.class); + + // Items and repository structure for testing + Community owningCommunity; + Collection collection; + Item itemOne; + Item itemTwo; + + // Some simple statement lists for testing + List trueStatements; + List trueFalseStatements; + List falseStatements; + LogicalStatement trueStatementOne; + LogicalStatement falseStatementOne; + + // Field and values used to set title metadata + String element = "title"; + String qualifier = null; + MetadataField metadataField; + + /** + * 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 { + context.turnOffAuthorisationSystem(); + // Set up logical statement lists for operator testing + setUpStatements(); + // Set up DSpace resources for condition and filter testing + this.owningCommunity = communityService.create(null, context); + this.collection = collectionService.create(context, owningCommunity); + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + this.itemOne = installItemService.installItem(context, workspaceItem); + workspaceItem = workspaceItemService.create(context, collection, false); + this.itemTwo = installItemService.installItem(context, workspaceItem); + // Initialise metadata field for later testing with both items + this.metadataField = metadataFieldService.findByElement(context, + MetadataSchemaEnum.DC.getName(), element, qualifier); + context.restoreAuthSystemState(); + } catch (AuthorizeException ex) { + log.error("Authorize Error in init", ex); + fail("Authorize Error in init: " + ex.getMessage()); + } catch (SQLException ex) { + log.error("SQL Error in init", ex); + fail("SQL Error in init: " + ex.getMessage()); + } + } + + /** + * This method will be run after every test as per @After. It will + * clean resources initialized by the @Before methods. + * + * Other methods can be annotated with @After here or in subclasses + * but no execution order is guaranteed + */ + @After + @Override + public void destroy() { + context.turnOffAuthorisationSystem(); + // Delete resources + try { + itemService.delete(context, itemOne); + itemService.delete(context, itemTwo); + itemService.delete(context, itemOne); + collectionService.delete(context, collection); + communityService.delete(context, owningCommunity); + } catch (Exception e) { + // ignore + } + context.restoreAuthSystemState(); + + // Set all class members to null + owningCommunity = null; + collection = null; + itemOne = null; + itemTwo = null; + trueStatements = null; + trueFalseStatements = null; + falseStatements = null; + trueStatementOne = null; + falseStatementOne = null; + element = null; + qualifier = null; + metadataField = null; + + super.destroy(); + } + + /** + * Test the AND operator with simple lists of logical statements + */ + @Test + public void testAndOperator() { + // Blank operator + And and = new And(); + // Try tests - the item can be null, as the statements are simply returning booleans themselves + try { + // Set to True, True (expect True) + and.setStatements(trueStatements); + assertTrue("AND operator did not return true for a list of true statements", + and.getResult(context, null)); + // Set to True, False (expect False) + and.setStatements(trueFalseStatements); + assertFalse("AND operator did not return false for a list of statements with at least one false", + and.getResult(context, null)); + // Set to False, False (expect False) + and.setStatements(falseStatements); + assertFalse("AND operator did not return false for a list of false statements", + and.getResult(context, null)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the AND operator" + e.getMessage()); + } + } + + /** + * Test the OR operator with simple lists of logical statements + */ + @Test + public void testOrOperator() { + // Blank operator + Or or = new Or(); + // Try tests - the item can be null, as the statements are simply returning booleans themselves + try { + // Set to True, True (expect True) + or.setStatements(trueStatements); + assertTrue("OR operator did not return true for a list of true statements", + or.getResult(context, null)); + // Set to True, False (expect True) + or.setStatements(trueFalseStatements); + assertTrue("OR operator did not return true for a list of statements with at least one false", + or.getResult(context, null)); + // Set to False, False (expect False) + or.setStatements(falseStatements); + assertFalse("OR operator did not return false for a list of false statements", + or.getResult(context, null)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the OR operator" + e.getMessage()); + } + } + + /** + * Test the NAND operator with simple lists of logical statements + */ + @Test + public void testNandOperator() { + // Blank operator + Nand nand = new Nand(); + // Try tests - the item can be null, as the statements are simply returning booleans themselves + try { + // Set to True, True (expect False) + nand.setStatements(trueStatements); + assertFalse("NAND operator did not return false for a list of true statements", + nand.getResult(context, null)); + // Set to True, False (expect True) + nand.setStatements(trueFalseStatements); + assertTrue("NAND operator did not return true for a list of statements with at least one false", + nand.getResult(context, null)); + // Set to False, False (expect True) + nand.setStatements(falseStatements); + assertTrue("NAND operator did not return true for a list of false statements", + nand.getResult(context, null)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the NAND operator" + e.getMessage()); + } + } + + /** + * Test the NOR operator with simple lists of logical statements + */ + @Test + public void testNorOperator() { + // Blank operator + Nor nor = new Nor(); + // Try tests - the item can be null, as the statements are simply returning booleans themselves + try { + // Set to True, True (expect False) + nor.setStatements(trueStatements); + assertFalse("NOR operator did not return false for a list of true statements", + nor.getResult(context, null)); + // Set to True, False (expect False) + nor.setStatements(trueFalseStatements); + assertFalse("NOR operator did not return false for a list of statements with a true and a false", + nor.getResult(context, null)); + // Set to False, False (expect True) + nor.setStatements(falseStatements); + assertTrue("NOR operator did not return true for a list of false statements", + nor.getResult(context, null)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the NOR operator" + e.getMessage()); + } + } + + /** + * Test the NOT operator with simple individual true/false statements + */ + @Test + public void testNotOperator() { + // Blank operator + Not not = new Not(); + // Try tests - the item can be null, as the statements are simply returning booleans themselves + try { + // Set to True (expect False) + not.setStatements(trueStatementOne); + assertFalse("NOT operator did not return false for a true statement", + not.getResult(context, null)); + // Set to False (expect True) + not.setStatements(falseStatementOne); + assertTrue("NOT operator did not return true for a false statement", + not.getResult(context, null)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the NOT operator" + e.getMessage()); + } + } + + /** + * Test a simple filter with a single logical statement: the MetadataValueMatchCondition + * looking for a dc.title field beginning with "TEST" + */ + @Test + public void testMetadataValueMatchCondition() { + try { + MetadataValue metadataValueOne = metadataValueService.create(context, itemOne, metadataField); + MetadataValue metadataValueTwo = metadataValueService.create(context, itemTwo, metadataField); + metadataValueOne.setValue("TEST title should match the condition"); + metadataValueTwo.setValue("This title should not match the condition"); + } catch (SQLException e) { + fail("Encountered SQL error creating metadata value on item: " + e.getMessage()); + } + + // Instantiate new filter for testing this condition + DefaultFilter metadataMatchFilter = new DefaultFilter(); + //Filter metadataMatchFilter = DSpaceServicesFactory.getInstance().getServiceManager() + // .getServiceByName("starts_with_title_filter", DefaultFilter.class); + log.debug("Filter class: " + metadataMatchFilter.getClass()); + // Create condition to match pattern on dc.title metadata + MetadataValueMatchCondition condition = new MetadataValueMatchCondition(); + condition.setItemService(ContentServiceFactory.getInstance().getItemService()); + Map parameters = new HashMap<>(); + // Match on the dc.title field + parameters.put("field", "dc.title"); + // "Starts with "TEST" (case sensitive) + parameters.put("pattern", "^TEST"); + // Set up condition with these parameters and add it as the sole statement to the metadata filter + try { + condition.setParameters(parameters); + metadataMatchFilter.setStatement(condition); + // Test the filter on the first item - expected outcome is true + assertTrue("itemOne unexpectedly did not match the 'dc.title starts with TEST' test", + metadataMatchFilter.getResult(context, itemOne)); + // Test the filter on the second item - expected outcome is false + assertFalse("itemTwo unexpectedly matched the 'dc.title starts with TEST' test", + metadataMatchFilter.getResult(context, itemTwo)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the MetadataValueMatchCondition filter" + e.getMessage()); + } + } + + /** + * Set up some simple statements for testing out operators + */ + private void setUpStatements() { + // Simple lambdas to define statements + // The two class members are used elsewhere, as direct statements for NOT testing + trueStatementOne = (context, item) -> true; + LogicalStatement trueStatementTwo = (context, item) -> true; + falseStatementOne = (context, item) -> false; + LogicalStatement falseStatementTwo = (context, item) -> false; + + // Create lists and add the statements + // True, True + trueStatements = new ArrayList<>(); + trueStatements.add(trueStatementOne); + trueStatements.add(trueStatementTwo); + // True, False + trueFalseStatements = new ArrayList<>(); + trueFalseStatements.add(trueStatementOne); + trueFalseStatements.add(falseStatementOne); + // False, False + falseStatements = new ArrayList<>(); + falseStatements.add(falseStatementOne); + falseStatements.add(falseStatementTwo); + } +} 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 d79ba60450..52cda18df5 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -34,12 +34,15 @@ import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.DefaultFilter; +import org.dspace.content.logic.LogicalStatement; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.identifier.doi.DOIConnector; import org.dspace.identifier.doi.DOIIdentifierException; +import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; import org.dspace.identifier.factory.IdentifierServiceFactory; import org.dspace.identifier.service.DOIService; import org.dspace.services.ConfigurationService; @@ -125,6 +128,7 @@ public class DOIIdentifierProviderTest provider.itemService = itemService; provider.setConfigurationService(config); provider.setDOIConnector(connector); + provider.setFilterService(null); } catch (AuthorizeException ex) { log.error("Authorization Error in init", ex); fail("Authorization Error in init: " + ex.getMessage()); @@ -499,8 +503,8 @@ public class DOIIdentifierProviderTest Item item = newItem(); String doi = null; try { - // get a DOI: - doi = provider.mint(context, item); + // get a DOI (skipping any filters) + doi = provider.mint(context, item, true); } catch (IdentifierException e) { e.printStackTrace(System.err); fail("Got an IdentifierException: " + e.getMessage()); @@ -530,6 +534,82 @@ public class DOIIdentifierProviderTest assertEquals("Mint did not returned an existing DOI!", doi, retrievedDOI); } + /** + * Test minting a DOI with a filter that always returns false and therefore never mints the DOI + */ + @Test + public void testMint_DOI_withNonMatchingFilter() + throws SQLException, AuthorizeException, IOException, IllegalAccessException, IdentifierException, + WorkflowException { + 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) + 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); + } 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); + } + + /** + * Test minting a DOI with a filter that always returns true and therefore allows the DOI to be minted + * (this should have hte same results as base testMint_DOI, but here we use an explicit filter rather than null) + */ + @Test + public void testMint_DOI_withMatchingFilter() + throws SQLException, AuthorizeException, IOException, IllegalAccessException, IdentifierException, + WorkflowException { + Item item = newItem(); + String doi = null; + boolean wasFiltered = false; + try { + // Temporarily set the provider to have a filter that always returns true for an item + // (therefore, the item is allowed to have a DOI minted) + 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); + } 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); + + // Continue with regular minting tests + assertNotNull("Minted DOI is null!", doi); + assertFalse("Minted DOI is empty!", doi.isEmpty()); + try { + doiService.formatIdentifier(doi); + } catch (Exception e) { + e.printStackTrace(); + fail("Minted an unrecognizable DOI: " + e.getMessage()); + } + } + + @Test public void testReserve_DOI() throws SQLException, SQLException, AuthorizeException, IOException, @@ -584,7 +664,8 @@ public class DOIIdentifierProviderTest IdentifierException, WorkflowException, IllegalAccessException { Item item = newItem(); - String doi = provider.register(context, item); + // Register, skipping the filter + String doi = provider.register(context, item, true); // we want the created DOI to be returned in the following format: // doi:10./. From 4e70bce553d379f6a532a2c90e9f2a10a3bfc442 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 24 Jun 2020 13:28:45 +1200 Subject: [PATCH 0017/1254] [DS-4522](main) Fix default configuration for curation task --- dspace/config/modules/doi-curation.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/modules/doi-curation.cfg b/dspace/config/modules/doi-curation.cfg index c9449b6520..95f14a2885 100644 --- a/dspace/config/modules/doi-curation.cfg +++ b/dspace/config/modules/doi-curation.cfg @@ -3,7 +3,7 @@ ## # Should any logical filters be skipped when registering DOIs? (ie. *always* register, never filter out the item) # Default: true -doi-curation.skip-filter = false +#doi-curation.skip-filter = true ## # Should we allow the curation task to be distributed over communities / collections of items or the whole repository? From 6fbc4dbd6ff42119ba10e0613bd754305e3f917c Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Wed, 24 Jun 2020 11:14:35 +0200 Subject: [PATCH 0018/1254] DS-4522: Add missing license header --- .../dspace/content/logic/LogicalStatementException.java | 7 +++++++ .../doi/DOIIdentifierNotApplicableException.java | 7 +++++++ 2 files changed, 14 insertions(+) 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 9fa561a148..9ddbb757b6 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 @@ -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.content.logic; /** diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java index a320baa806..ab65e69db5 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.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.identifier.doi; /** From 1bb5e35f04d210581f8fab88fae903bbcb0955cb Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 16 Mar 2021 13:32:09 +1300 Subject: [PATCH 0019/1254] [DS-4522] Refactor boolean canMint() to void checkMintable() as per PR review --- .../identifier/DOIIdentifierProvider.java | 21 ++++++------------- .../FilteredIdentifierProvider.java | 3 +-- 2 files changed, 7 insertions(+), 17 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 8b5289f0c1..3bd396d1b9 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -905,7 +905,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @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 canMint() check + * @param skipFilter - Whether or not to skip the filters for the checkMintable() check * @return * @throws SQLException * @throws DOIIdentifierException @@ -964,9 +964,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } 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 - boolean canMintDOI = canMint(context, dso); - log.debug("Called canMint(), result was " + canMintDOI + - " (and presumably an exception was not thrown)"); + checkMintable(context, dso); } // check prefix @@ -986,10 +984,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { 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 prohibeted by a filter - boolean canMintDOI = canMint(context, dso); - log.debug("Called canMint(), result was " + canMintDOI + - " (and presumably an exception was not thrown)"); + // throws an exception if creation of a new DOI is prohibited by a filter + checkMintable(context, dso); } doi = doiService.create(context); @@ -1101,14 +1097,11 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * 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 - * @return * @throws DOIIdentifierNotApplicableException */ @Override - public Boolean canMint(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException { - // Default is 'true' in the case of a null/missing filter. All we really care about is whether - // an exception was thrown or not. - log.debug("canMint is being called"); + public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException { + // 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")) { try { @@ -1125,7 +1118,5 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } else { log.debug("DOI Identifier Provider: filterService is null"); } - - return true; } } \ 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 a92475529f..73d7f86641 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java @@ -84,10 +84,9 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider { * Check configured item filters to see if this identifier is allowed to be minted * @param context - DSpace context * @param dso - DSpaceObject to be inspected - * @return a Boolean indicating whether an identifier may be minted for this item (after filters are applied) * @throws IdentifierException */ - public abstract Boolean canMint(Context context, DSpaceObject dso) throws IdentifierException; + public abstract void checkMintable(Context context, DSpaceObject dso) throws IdentifierException; } From 5096c66437bb9fa013aeaf4d45ff1f9afa3ccae8 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 16 Mar 2021 13:39:46 +1300 Subject: [PATCH 0020/1254] [DS-4522] Expand debug log message to explain what happens when filter service is null --- .../main/java/org/dspace/identifier/DOIIdentifierProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3bd396d1b9..1708535c27 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -1116,7 +1116,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { throw new DOIIdentifierNotApplicableException(e); } } else { - log.debug("DOI Identifier Provider: filterService is null"); + log.debug("DOI Identifier Provider: filterService is null (ie. don't prevent DOI minting)"); } } } \ No newline at end of file From 692dbe7fadb7879f91c8ec84fd0fa95f6cd5b919 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 16 Mar 2021 13:40:52 +1300 Subject: [PATCH 0021/1254] [DS-4522] Make LogicalStatementException unchecked as per PR review --- .../org/dspace/content/logic/LogicalStatementException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9ddbb757b6..758a0a7124 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 @@ -14,7 +14,7 @@ package org.dspace.content.logic; * @author Kim Shepherd * @version $Revision$ */ -public class LogicalStatementException extends Exception { +public class LogicalStatementException extends RuntimeException { public LogicalStatementException() { super(); From f74f4e3b18e9a86d15b1a2a9d9c1ce1a850166f8 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 16 Mar 2021 13:50:06 +1300 Subject: [PATCH 0022/1254] [DS-4522] Remove fetchccdata.cfg include (accidentally kept during rebase vs main) --- dspace/config/dspace.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 087a7a0800..3a9da44642 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1525,7 +1525,6 @@ include = ${module_dir}/clamav.cfg include = ${module_dir}/curate.cfg include = ${module_dir}/discovery.cfg include = ${module_dir}/doi-curation.cfg -include = ${module_dir}/fetchccdata.cfg include = ${module_dir}/google-analytics.cfg include = ${module_dir}/healthcheck.cfg include = ${module_dir}/irus-statistics.cfg From a770e7996ee9ecd06b404b2a27b28502dbdf9153 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 16 Mar 2021 14:32:11 +1300 Subject: [PATCH 0023/1254] [DS-4522] Apply bugfix to InCollectionCondition - better 'owning collection' check --- .../condition/InCollectionCondition.java | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) 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 78a257dd75..4ec85982d5 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 @@ -7,13 +7,17 @@ */ package org.dspace.content.logic.condition; +import java.sql.SQLException; import java.util.List; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.logic.LogicalStatementException; import org.dspace.content.service.CollectionService; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; @@ -25,10 +29,11 @@ import org.springframework.beans.factory.annotation.Autowired; * @version $Revision$ */ public class InCollectionCondition extends AbstractCondition { - private static Logger log = Logger.getLogger(InCollectionCondition.class); + private static Logger log = LogManager.getLogger(InCollectionCondition.class); @Autowired(required = true) protected CollectionService collectionService; + protected ItemService itemService; /** * Return true if item is in one of the specified collections @@ -42,8 +47,10 @@ public class InCollectionCondition extends AbstractCondition { public Boolean getResult(Context context, Item item) throws LogicalStatementException { List collectionHandles = (List)getParameters().get("collections"); - List itemCollections = item.getCollections(); + // Look for the handle among an archived item's collections - this test will only work after submission + // and archival is complete + List itemCollections = item.getCollections(); for (Collection collection : itemCollections) { if (collectionHandles.contains(collection.getHandle())) { log.debug("item " + item.getHandle() + " is in collection " @@ -52,6 +59,27 @@ public class InCollectionCondition extends AbstractCondition { } } + // Look for the parent object of the item. This is important as the item.getOwningCollection method + // may return null, even though the item itself does have a parent object, at the point of archival + try { + DSpaceObject parent = itemService.getParentObject(context, item); + if (parent != null) { + log.debug("Got parent DSO for item: " + parent.getID().toString()); + log.debug("Parent DSO handle: " + parent.getHandle()); + if (collectionHandles.contains(parent.getHandle())) { + log.debug("item " + item.getHandle() + " is in collection " + + parent.getHandle() + ", returning true"); + return true; + } + } else { + log.debug("Parent DSO is null..."); + } + } catch (SQLException e) { + log.error("Error obtaining parent DSO", e); + throw new LogicalStatementException(e); + } + + // If we reach this statement, the item did not appear in any of the collections from the parameters log.debug("item " + item.getHandle() + " not found in the passed collection handle list"); return false; From da844bc03db360694954ae3602449e064e5e1ed9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 16 Mar 2021 15:53:07 +1300 Subject: [PATCH 0024/1254] [DS-4522] Apply 'owning collection' fix to InCommunityCondition collection iteration --- .../logic/condition/InCommunityCondition.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) 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 a57fd2a5fd..b2a90ee325 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 @@ -13,6 +13,7 @@ import java.util.List; import org.apache.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.content.logic.LogicalStatementException; import org.dspace.core.Context; @@ -41,6 +42,7 @@ public class InCommunityCondition extends AbstractCondition { List communityHandles = (List)getParameters().get("communities"); List itemCollections = item.getCollections(); + // Check communities of item.getCollections() - this will only see collections if the item is archived for (Collection collection : itemCollections) { try { List communities = collection.getCommunities(); @@ -55,6 +57,34 @@ public class InCommunityCondition extends AbstractCondition { } } + // Look for the parent object of the item. This is important as the item.getOwningCollection method + // may return null, even though the item itself does have a parent object, at the point of archival + try { + DSpaceObject parent = itemService.getParentObject(context, item); + if (parent instanceof Collection) { + log.debug("Got parent DSO for item: " + parent.getID().toString()); + log.debug("Parent DSO handle: " + parent.getHandle()); + try { + // Now iterate communities of this parent collection + Collection collection = (Collection)parent; + List communities = collection.getCommunities(); + for (Community community : communities) { + if (communityHandles.contains(community.getHandle())) { + return true; + } + } + } catch (SQLException e) { + log.error(e.getMessage()); + throw new LogicalStatementException(e); + } + } else { + log.debug("Parent DSO is null or is not a Collection..."); + } + } catch (SQLException e) { + log.error("Error obtaining parent DSO", e); + throw new LogicalStatementException(e); + } + return false; } } From 5a853092200662404570424c7d370d9e08222b0e Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 16 Mar 2021 15:53:38 +1300 Subject: [PATCH 0025/1254] [DS-4522] Apply parameter, item, context checks as per PR review --- .../logic/condition/AbstractCondition.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) 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 8c474fbbfd..79248711e0 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 @@ -7,9 +7,10 @@ */ package org.dspace.content.logic.condition; -import java.util.HashMap; import java.util.Map; +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.content.service.CollectionService; @@ -25,8 +26,11 @@ import org.springframework.beans.factory.annotation.Autowired; * @version $Revision$ */ public abstract class AbstractCondition implements Condition { - private Map parameters = new HashMap<>(); + // Parameters map (injected, required -- see setter annotation) + private Map parameters; + + // Declare and instantiate spring services @Autowired(required = true) protected ItemService itemService; @Autowired(required = true) @@ -34,6 +38,9 @@ public abstract class AbstractCondition implements Condition { @Autowired(required = true) protected HandleService handleService; + // Logging + Logger log = LogManager.getLogger(AbstractCondition.class); + /** * Get parameters set by spring configuration in item-filters.xml * These could be any kind of map that the extending condition class needs for evaluation @@ -51,6 +58,7 @@ public abstract class AbstractCondition implements Condition { * @param parameters * @throws LogicalStatementException */ + @Autowired(required = true) @Override public void setParameters(Map parameters) throws LogicalStatementException { this.parameters = parameters; @@ -66,13 +74,11 @@ public abstract class AbstractCondition implements Condition { @Override public Boolean getResult(Context context, Item item) throws LogicalStatementException { if (item == null) { - throw new LogicalStatementException("Item is null"); + log.error("Error evaluating item. Passed item is null, returning false"); + return false; } if (context == null) { - throw new LogicalStatementException("Context is null"); - } - if (this.parameters == null) { - throw new LogicalStatementException("Parameters are null"); + throw new IllegalStateException("Context is null"); } return true; } From 0abee654240a9307a4e5e7335f12e6e98f4a9035 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 16 Mar 2021 15:54:21 +1300 Subject: [PATCH 0026/1254] [DS-4522] Add unit tests for Bitstream, InCollection, InCommunity, IsWithdrawn conds --- .../content/logic/LogicalFilterTest.java | 229 +++++++++++++++--- 1 file changed, 201 insertions(+), 28 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java index c0e8b84d50..6ae761ea83 100644 --- a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java +++ b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java @@ -11,6 +11,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; @@ -20,6 +23,7 @@ import java.util.Map; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -28,12 +32,15 @@ import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.condition.InCollectionCondition; import org.dspace.content.logic.condition.MetadataValueMatchCondition; import org.dspace.content.logic.operator.And; import org.dspace.content.logic.operator.Nand; import org.dspace.content.logic.operator.Nor; import org.dspace.content.logic.operator.Not; import org.dspace.content.logic.operator.Or; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.InstallItemService; @@ -52,6 +59,8 @@ import org.junit.Test; public class LogicalFilterTest extends AbstractUnitTest { // Required services protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); @@ -63,10 +72,13 @@ public class LogicalFilterTest extends AbstractUnitTest { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LogicalFilterTest.class); // Items and repository structure for testing - Community owningCommunity; - Collection collection; + Community communityOne; + Community communityTwo; + Collection collectionOne; + Collection collectionTwo; Item itemOne; Item itemTwo; + Item itemThree; // Some simple statement lists for testing List trueStatements; @@ -96,22 +108,48 @@ public class LogicalFilterTest extends AbstractUnitTest { // Set up logical statement lists for operator testing setUpStatements(); // Set up DSpace resources for condition and filter testing - this.owningCommunity = communityService.create(null, context); - this.collection = collectionService.create(context, owningCommunity); - WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + // Set up first community, collection and item + this.communityOne = communityService.create(null, context); + this.collectionOne = collectionService.create(context, communityOne); + WorkspaceItem workspaceItem = workspaceItemService.create(context, collectionOne, false); this.itemOne = installItemService.installItem(context, workspaceItem); - workspaceItem = workspaceItemService.create(context, collection, false); + // Add one bitstream to item one, but put it in THUMBNAIL bundle + bundleService.addBitstream(context, bundleService.create(context, itemTwo, "THUMBNAIL"), + bitstreamService.create(context, + new ByteArrayInputStream("Item 1 Thumbnail 1".getBytes(StandardCharsets.UTF_8)))); + // Set up second community, collection and item, and third item + this.communityTwo = communityService.create(null, context); + this.collectionTwo = collectionService.create(context, communityTwo); + // Item two + workspaceItem = workspaceItemService.create(context, collectionTwo, false); this.itemTwo = installItemService.installItem(context, workspaceItem); + // Add two bitstreams to item two + Bundle bundleTwo = bundleService.create(context, itemTwo, "ORIGINAL"); + bundleService.addBitstream(context, bundleTwo, bitstreamService.create(context, + new ByteArrayInputStream("Item 2 Bitstream 1".getBytes(StandardCharsets.UTF_8)))); + bundleService.addBitstream(context, bundleTwo, bitstreamService.create(context, + new ByteArrayInputStream("Item 2 Bitstream 2".getBytes(StandardCharsets.UTF_8)))); + // Item three + workspaceItem = workspaceItemService.create(context, collectionTwo, false); + this.itemThree = installItemService.installItem(context, workspaceItem); + // Add three bitstreams to item three + Bundle bundleThree = bundleService.create(context, itemThree, "ORIGINAL"); + bundleService.addBitstream(context, bundleThree, bitstreamService.create(context, + new ByteArrayInputStream("Item 3 Bitstream 1".getBytes(StandardCharsets.UTF_8)))); + bundleService.addBitstream(context, bundleThree, bitstreamService.create(context, + new ByteArrayInputStream("Item 3 Bitstream 2".getBytes(StandardCharsets.UTF_8)))); + bundleService.addBitstream(context, bundleThree, bitstreamService.create(context, + new ByteArrayInputStream("Item 3 Bitstream 2".getBytes(StandardCharsets.UTF_8)))); + + // Withdraw the second item for later testing + itemService.withdraw(context, itemTwo); // Initialise metadata field for later testing with both items this.metadataField = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), element, qualifier); context.restoreAuthSystemState(); - } catch (AuthorizeException ex) { - log.error("Authorize Error in init", ex); - fail("Authorize Error in init: " + ex.getMessage()); - } catch (SQLException ex) { - log.error("SQL Error in init", ex); - fail("SQL Error in init: " + ex.getMessage()); + } catch (AuthorizeException | SQLException | IOException e) { + log.error("Error encountered during init", e); + fail("Error encountered during init: " + e.getMessage()); } } @@ -130,19 +168,25 @@ public class LogicalFilterTest extends AbstractUnitTest { try { itemService.delete(context, itemOne); itemService.delete(context, itemTwo); - itemService.delete(context, itemOne); - collectionService.delete(context, collection); - communityService.delete(context, owningCommunity); + itemService.delete(context, itemThree); + collectionService.delete(context, collectionOne); + collectionService.delete(context, collectionTwo); + communityService.delete(context, communityOne); + communityService.delete(context, communityTwo); } catch (Exception e) { // ignore + log.error("Error cleaning up test resources: " + e.getMessage()); } context.restoreAuthSystemState(); // Set all class members to null - owningCommunity = null; - collection = null; + communityOne = null; + communityTwo = null; + collectionOne = null; + collectionTwo = null; itemOne = null; itemTwo = null; + itemThree = null; trueStatements = null; trueFalseStatements = null; falseStatements = null; @@ -167,15 +211,15 @@ public class LogicalFilterTest extends AbstractUnitTest { // Set to True, True (expect True) and.setStatements(trueStatements); assertTrue("AND operator did not return true for a list of true statements", - and.getResult(context, null)); + and.getResult(context, itemOne)); // Set to True, False (expect False) and.setStatements(trueFalseStatements); assertFalse("AND operator did not return false for a list of statements with at least one false", - and.getResult(context, null)); + and.getResult(context, itemOne)); // Set to False, False (expect False) and.setStatements(falseStatements); assertFalse("AND operator did not return false for a list of false statements", - and.getResult(context, null)); + and.getResult(context, itemOne)); } catch (LogicalStatementException e) { log.error(e.getMessage()); fail("LogicalStatementException thrown testing the AND operator" + e.getMessage()); @@ -288,7 +332,7 @@ public class LogicalFilterTest extends AbstractUnitTest { /** * Test a simple filter with a single logical statement: the MetadataValueMatchCondition - * looking for a dc.title field beginning with "TEST" + * looking for a dc.title field beginning with "TEST", and an item that doesn't match this test */ @Test public void testMetadataValueMatchCondition() { @@ -302,10 +346,8 @@ public class LogicalFilterTest extends AbstractUnitTest { } // Instantiate new filter for testing this condition - DefaultFilter metadataMatchFilter = new DefaultFilter(); - //Filter metadataMatchFilter = DSpaceServicesFactory.getInstance().getServiceManager() - // .getServiceByName("starts_with_title_filter", DefaultFilter.class); - log.debug("Filter class: " + metadataMatchFilter.getClass()); + DefaultFilter filter = new DefaultFilter(); + // Create condition to match pattern on dc.title metadata MetadataValueMatchCondition condition = new MetadataValueMatchCondition(); condition.setItemService(ContentServiceFactory.getInstance().getItemService()); @@ -317,19 +359,150 @@ public class LogicalFilterTest extends AbstractUnitTest { // Set up condition with these parameters and add it as the sole statement to the metadata filter try { condition.setParameters(parameters); - metadataMatchFilter.setStatement(condition); + filter.setStatement(condition); // Test the filter on the first item - expected outcome is true assertTrue("itemOne unexpectedly did not match the 'dc.title starts with TEST' test", - metadataMatchFilter.getResult(context, itemOne)); + filter.getResult(context, itemOne)); // Test the filter on the second item - expected outcome is false assertFalse("itemTwo unexpectedly matched the 'dc.title starts with TEST' test", - metadataMatchFilter.getResult(context, itemTwo)); + filter.getResult(context, itemTwo)); } catch (LogicalStatementException e) { log.error(e.getMessage()); fail("LogicalStatementException thrown testing the MetadataValueMatchCondition filter" + e.getMessage()); } } + /** + * Test a simple filter with a single logical statement: the InCollectionCondition + * looking for an item that is in collectionOne, and one that is not in collectionOne + */ + @Test + public void testInCollectionCondition() { + // Instantiate new filter for testing this condition + DefaultFilter filter = new DefaultFilter(); + InCollectionCondition condition = new InCollectionCondition(); + condition.setItemService(ContentServiceFactory.getInstance().getItemService()); + Map parameters = new HashMap<>(); + + // Add collectionOne handle to the collections parameter - ie. we are testing to see if the item is + // in collectionOne only + List collections = new ArrayList<>(); + collections.add(collectionOne.getHandle()); + parameters.put("collections", collections); + + try { + // Set parameters and condition + condition.setParameters(parameters); + filter.setStatement(condition); + + // Test the filter on the first item - this item is in collectionOne: expected outcome is true + assertTrue("itemOne unexpectedly did not match the 'item in collectionOne' test", + filter.getResult(context, itemOne)); + // Test the filter on the second item - this item is NOT in collectionOne: expected outcome is false + assertFalse("itemTwo unexpectedly matched the 'item in collectionOne' test", + filter.getResult(context, itemTwo)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the InCollectionCondition filter" + e.getMessage()); + } + } + + /** + * Test a simple filter with a single logical statement: the InCommunityCondition + * looking for an item that is in communityOne, and one that is not in communityOne + */ + @Test + public void testInCommunityCondition() { + // Instantiate new filter for testing this condition + DefaultFilter filter = new DefaultFilter(); + InCollectionCondition condition = new InCollectionCondition(); + condition.setItemService(ContentServiceFactory.getInstance().getItemService()); + Map parameters = new HashMap<>(); + + // Add collectionOne handle to the collections parameter - ie. we are testing to see if the item is + // in collectionOne only + List collections = new ArrayList<>(); + collections.add(communityOne.getHandle()); + parameters.put("communities", collections); + + try { + // Set parameters and condition + condition.setParameters(parameters); + filter.setStatement(condition); + + // Test the filter on the first item - this item is in communityOne: expected outcome is true + assertTrue("itemOne unexpectedly did not match the 'item in communityOne' test", + filter.getResult(context, itemOne)); + // Test the filter on the second item - this item is NOT in communityOne: expected outcome is false + assertFalse("itemTwo unexpectedly matched the 'item in communityOne' test", + filter.getResult(context, itemTwo)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the InCommunityCondition filter" + e.getMessage()); + } + } + + /** + * Test a simple filter with the IsWithdrawnCondition. During setup, itemTwo was withdrawn. + */ + @Test + public void testIsWithdrawnCondition() { + // Instantiate new filter for testing this condition + DefaultFilter filter = new DefaultFilter(); + InCollectionCondition condition = new InCollectionCondition(); + + try { + condition.setItemService(ContentServiceFactory.getInstance().getItemService()); + condition.setParameters(new HashMap<>()); + filter.setStatement(condition); + + // Test the filter on itemOne - this item is not withdrawn: expected outcome is false + assertFalse("itemOne unexpectedly matched the 'item is withdrawn' test", + filter.getResult(context, itemOne)); + // Test the filter on itemTwo - this item was withdrawn in setup: expected outcome is true + assertTrue("itemTwo unexpectedly did NOT match the 'item is withdrawn' test", + filter.getResult(context, itemTwo)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the IsWithdrawnCondition filter" + e.getMessage()); + } + } + + /** + * Test a simple filter with the BitstreamCountCondition. + */ + @Test + public void testBitstreamCountCondition() { + // Instantiate new filter for testing this condition + DefaultFilter filter = new DefaultFilter(); + InCollectionCondition condition = new InCollectionCondition(); + + try { + condition.setItemService(ContentServiceFactory.getInstance().getItemService()); + + // Set parameters to check for items with at least 1 and at most 2 bitstreams in the ORIGINAL bundle + Map parameters = new HashMap<>(); + parameters.put("bundle", "ORIGINAL"); + parameters.put("min", 1); + parameters.put("max", 2); + condition.setParameters(new HashMap<>()); + filter.setStatement(condition); + + // Test the filter on itemOne - this item has one THUMBNAIL but zero ORIGINAL bitstreams: expect false + assertFalse("itemOne unexpectedly matched the '>=1 and <=2 ORIGINAL bitstreams' test" + + " (it has zero ORIGINAL bitstreams)", filter.getResult(context, itemOne)); + // Test the filter on itemTwo - this item has two ORIGINAL bitstreams: expect true + assertTrue("itemTwo unexpectedly did NOT match the '>=1 and <=2 ORIGINAL bitstreams' test" + + " (it has 2 ORIGINAL bitstreams)", filter.getResult(context, itemTwo)); + // Test the filter on itemTwo - this item has three ORIGINAL bitstreams: expect false + assertFalse("itemThree unexpectedly did NOT match the '>=1 and <=2 ORIGINAL bitstreams' test" + + " (it has 3 ORIGINAL bitstreams)", filter.getResult(context, itemThree)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the IsWithdrawnCondition filter" + e.getMessage()); + } + } + /** * Set up some simple statements for testing out operators */ From b51a894361866562450b114ef750bb58de7e5d20 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 23 Mar 2021 13:52:02 +1300 Subject: [PATCH 0027/1254] [DS-4522] Add unit tests for remaining conditions, fix service instantiation for tests --- .../logic/condition/AbstractCondition.java | 9 +- .../condition/InCollectionCondition.java | 4 - .../MetadataValuesMatchCondition.java | 2 - .../condition/ReadableByGroupCondition.java | 5 +- .../content/logic/LogicalFilterTest.java | 139 ++++++++++++++++-- 5 files changed, 131 insertions(+), 28 deletions(-) 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 79248711e0..255cbc7906 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 @@ -12,6 +12,7 @@ import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.logic.LogicalStatementException; import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; @@ -31,10 +32,10 @@ public abstract class AbstractCondition implements Condition { private Map parameters; // Declare and instantiate spring services - @Autowired(required = true) - protected ItemService itemService; - @Autowired(required = true) - protected CollectionService collectionService; + //@Autowired(required = true) + protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + //@Autowired(required = true) + protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); @Autowired(required = true) protected HandleService handleService; 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 4ec85982d5..9416a8c42f 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 @@ -31,10 +31,6 @@ import org.springframework.beans.factory.annotation.Autowired; public class InCollectionCondition extends AbstractCondition { private static Logger log = LogManager.getLogger(InCollectionCondition.class); - @Autowired(required = true) - protected CollectionService collectionService; - protected ItemService itemService; - /** * Return true if item is in one of the specified collections * Return false if not 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 894b5213e1..76dbd08150 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 @@ -27,8 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired; * @version $Revision$ */ public class MetadataValuesMatchCondition extends AbstractCondition { - @Autowired(required = true) - protected ItemService itemService; private static Logger log = Logger.getLogger(MetadataValuesMatchCondition.class); 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 335cc03782..d8b82253ba 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 @@ -12,6 +12,7 @@ import java.util.List; import org.apache.log4j.Logger; import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.content.logic.LogicalStatementException; @@ -29,8 +30,8 @@ import org.springframework.beans.factory.annotation.Autowired; public class ReadableByGroupCondition extends AbstractCondition { private static Logger log = Logger.getLogger(ReadableByGroupCondition.class); - @Autowired(required = true) - AuthorizeService authorizeService; + // Authorize service + AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); /** * Return true if this item allows a specified action (eg READ, WRITE, ADD) by a specified group diff --git a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java index 6ae761ea83..593546ec8b 100644 --- a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java +++ b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java @@ -23,6 +23,8 @@ import java.util.Map; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -32,8 +34,14 @@ import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.condition.BitstreamCountCondition; +import org.dspace.content.logic.condition.Condition; import org.dspace.content.logic.condition.InCollectionCondition; +import org.dspace.content.logic.condition.InCommunityCondition; +import org.dspace.content.logic.condition.IsWithdrawnCondition; import org.dspace.content.logic.condition.MetadataValueMatchCondition; +import org.dspace.content.logic.condition.MetadataValuesMatchCondition; +import org.dspace.content.logic.condition.ReadableByGroupCondition; import org.dspace.content.logic.operator.And; import org.dspace.content.logic.operator.Nand; import org.dspace.content.logic.operator.Nor; @@ -48,6 +56,10 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataValueService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -67,6 +79,8 @@ public class LogicalFilterTest extends AbstractUnitTest { protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); private MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); private MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); + private AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); // Logger private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LogicalFilterTest.class); @@ -114,7 +128,7 @@ public class LogicalFilterTest extends AbstractUnitTest { WorkspaceItem workspaceItem = workspaceItemService.create(context, collectionOne, false); this.itemOne = installItemService.installItem(context, workspaceItem); // Add one bitstream to item one, but put it in THUMBNAIL bundle - bundleService.addBitstream(context, bundleService.create(context, itemTwo, "THUMBNAIL"), + bundleService.addBitstream(context, bundleService.create(context, itemOne, "THUMBNAIL"), bitstreamService.create(context, new ByteArrayInputStream("Item 1 Thumbnail 1".getBytes(StandardCharsets.UTF_8)))); // Set up second community, collection and item, and third item @@ -349,7 +363,7 @@ public class LogicalFilterTest extends AbstractUnitTest { DefaultFilter filter = new DefaultFilter(); // Create condition to match pattern on dc.title metadata - MetadataValueMatchCondition condition = new MetadataValueMatchCondition(); + Condition condition = new MetadataValueMatchCondition(); condition.setItemService(ContentServiceFactory.getInstance().getItemService()); Map parameters = new HashMap<>(); // Match on the dc.title field @@ -372,6 +386,58 @@ public class LogicalFilterTest extends AbstractUnitTest { } } + /** + * Test a simple filter with a single logical statement: the MetadataValuesMatchCondition + * looking for a dc.title field beginning with "TEST" or "ALSO", and an item that doesn't match this test + */ + @Test + public void testMetadataValuesMatchCondition() { + try { + MetadataValue metadataValueOne = metadataValueService.create(context, itemOne, metadataField); + MetadataValue metadataValueTwo = metadataValueService.create(context, itemTwo, metadataField); + MetadataValue metadataValueThree = metadataValueService.create(context, itemThree, metadataField); + metadataValueOne.setValue("TEST this title should match the condition"); + metadataValueTwo.setValue("This title should match the condition, yEs"); + metadataValueThree.setValue("This title should not match the condition"); + } catch (SQLException e) { + fail("Encountered SQL error creating metadata value on item: " + e.getMessage()); + } + + // Instantiate new filter for testing this condition + DefaultFilter filter = new DefaultFilter(); + + // Create condition to match pattern on dc.title metadata + Condition condition = new MetadataValuesMatchCondition(); + Map parameters = new HashMap<>(); + // Match on the dc.title field + parameters.put("field", "dc.title"); + + List patterns = new ArrayList<>(); + // "Starts with "TEST" (case sensitive) + patterns.add("^TEST"); + // "Ends with 'yes' (case insensitive) + patterns.add("(?i)yes$"); + // Add the list of possible patterns + parameters.put("patterns", patterns); + // Set up condition with these parameters and add it as the sole statement to the metadata filter + try { + condition.setParameters(parameters); + filter.setStatement(condition); + // Test the filter on the first item - expected outcome is true + assertTrue("itemOne unexpectedly did not match the " + + "'dc.title starts with TEST or ends with yes' test", filter.getResult(context, itemOne)); + // Test the filter on the second item - expected outcome is true + assertTrue("itemTwo unexpectedly did not match the " + + "'dc.title starts with TEST or ends with yes' test", filter.getResult(context, itemTwo)); + // Test the filter on the third item - expected outcome is false + assertFalse("itemThree unexpectedly matched the " + + "'dc.title starts with TEST or ends with yes' test", filter.getResult(context, itemThree)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the MetadataValuesMatchCondition filter" + e.getMessage()); + } + } + /** * Test a simple filter with a single logical statement: the InCollectionCondition * looking for an item that is in collectionOne, and one that is not in collectionOne @@ -380,8 +446,7 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testInCollectionCondition() { // Instantiate new filter for testing this condition DefaultFilter filter = new DefaultFilter(); - InCollectionCondition condition = new InCollectionCondition(); - condition.setItemService(ContentServiceFactory.getInstance().getItemService()); + Condition condition = new InCollectionCondition(); Map parameters = new HashMap<>(); // Add collectionOne handle to the collections parameter - ie. we are testing to see if the item is @@ -415,15 +480,15 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testInCommunityCondition() { // Instantiate new filter for testing this condition DefaultFilter filter = new DefaultFilter(); - InCollectionCondition condition = new InCollectionCondition(); + Condition condition = new InCommunityCondition(); condition.setItemService(ContentServiceFactory.getInstance().getItemService()); Map parameters = new HashMap<>(); - // Add collectionOne handle to the collections parameter - ie. we are testing to see if the item is - // in collectionOne only - List collections = new ArrayList<>(); - collections.add(communityOne.getHandle()); - parameters.put("communities", collections); + // Add communitynOne handle to the communities parameter - ie. we are testing to see if the item is + // in communityOne only + List communities = new ArrayList<>(); + communities.add(communityOne.getHandle()); + parameters.put("communities", communities); try { // Set parameters and condition @@ -449,7 +514,7 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testIsWithdrawnCondition() { // Instantiate new filter for testing this condition DefaultFilter filter = new DefaultFilter(); - InCollectionCondition condition = new InCollectionCondition(); + Condition condition = new IsWithdrawnCondition(); try { condition.setItemService(ContentServiceFactory.getInstance().getItemService()); @@ -475,7 +540,7 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testBitstreamCountCondition() { // Instantiate new filter for testing this condition DefaultFilter filter = new DefaultFilter(); - InCollectionCondition condition = new InCollectionCondition(); + Condition condition = new BitstreamCountCondition(); try { condition.setItemService(ContentServiceFactory.getInstance().getItemService()); @@ -483,9 +548,9 @@ public class LogicalFilterTest extends AbstractUnitTest { // Set parameters to check for items with at least 1 and at most 2 bitstreams in the ORIGINAL bundle Map parameters = new HashMap<>(); parameters.put("bundle", "ORIGINAL"); - parameters.put("min", 1); - parameters.put("max", 2); - condition.setParameters(new HashMap<>()); + parameters.put("min", String.valueOf(1)); + parameters.put("max", String.valueOf(2)); + condition.setParameters(parameters); filter.setStatement(condition); // Test the filter on itemOne - this item has one THUMBNAIL but zero ORIGINAL bitstreams: expect false @@ -499,7 +564,49 @@ public class LogicalFilterTest extends AbstractUnitTest { " (it has 3 ORIGINAL bitstreams)", filter.getResult(context, itemThree)); } catch (LogicalStatementException e) { log.error(e.getMessage()); - fail("LogicalStatementException thrown testing the IsWithdrawnCondition filter" + e.getMessage()); + fail("LogicalStatementException thrown testing the IsWithdrawnCondition filter: " + e.getMessage()); + } + } + + /** + * Test a simple filter using the ReadableByGroupCondition + */ + @Test + public void testReadableByGroupCondition() { + // Instantiate new filter for testing this condition + DefaultFilter filter = new DefaultFilter(); + Condition condition = new ReadableByGroupCondition(); + + try { + condition.setItemService(ContentServiceFactory.getInstance().getItemService()); + + // Make item one readable by Test Group + try { + context.turnOffAuthorisationSystem(); + Group g = groupService.create(context); + groupService.setName(g, "Test Group"); + groupService.update(context, g); + authorizeService.addPolicy(context, itemOne, Constants.READ, g); + context.restoreAuthSystemState(); + } catch (AuthorizeException | SQLException e) { + fail("Exception thrown adding group READ policy to item: " + itemOne + ": " + e.getMessage()); + } + // Set parameters to check for items with Anonymous READ permission + Map parameters = new HashMap<>(); + parameters.put("group", "Test Group"); + parameters.put("action", "READ"); + condition.setParameters(parameters); + filter.setStatement(condition); + + // Test the filter on itemOne - this item was explicitly set with expected group READ policy + assertTrue("itemOne unexpectedly did not match the 'is readable by Test Group' test", + filter.getResult(context, itemOne)); + // Test the filter on itemTwo - this item has no policies: expect false + assertFalse("itemTwo unexpectedly matched the 'is readable by Test Group' test", + filter.getResult(context, itemTwo)); + } catch (LogicalStatementException e) { + log.error(e.getMessage()); + fail("LogicalStatementException thrown testing the ReadableByGroup filter" + e.getMessage()); } } From a3beda1a055d071ade4e0f2e1b6de1c5afdca991 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 23 Mar 2021 13:53:21 +1300 Subject: [PATCH 0028/1254] [DS-4522] Remove unused imports --- .../dspace/content/logic/condition/InCollectionCondition.java | 3 --- .../content/logic/condition/MetadataValuesMatchCondition.java | 2 -- .../content/logic/condition/ReadableByGroupCondition.java | 1 - 3 files changed, 6 deletions(-) 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 9416a8c42f..35f4fd2815 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 @@ -16,10 +16,7 @@ import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.logic.LogicalStatementException; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; /** * A condition that accepts a list of collection handles and returns true 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 76dbd08150..1fd30c4e88 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 @@ -15,9 +15,7 @@ import org.apache.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.logic.LogicalStatementException; -import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; /** * A condition that returns true if any pattern in a list of patterns matches any value 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 d8b82253ba..ae61bce2a6 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 @@ -18,7 +18,6 @@ import org.dspace.content.Item; import org.dspace.content.logic.LogicalStatementException; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; /** * A condition that accepts a group and action parameter and returns true if the group From cc1f67734e510adb7ffe19df31e4442d08ac152f Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 23 Mar 2021 14:02:25 +1300 Subject: [PATCH 0029/1254] [DS-4522] Refactor LogicalStatement.getResult() type to primitive boolean --- .../src/main/java/org/dspace/content/logic/DefaultFilter.java | 2 +- dspace-api/src/main/java/org/dspace/content/logic/Filter.java | 2 +- .../main/java/org/dspace/content/logic/LogicalStatement.java | 2 +- .../main/java/org/dspace/content/logic/TestLogicRunner.java | 4 ++-- .../org/dspace/content/logic/condition/AbstractCondition.java | 2 +- .../content/logic/condition/BitstreamCountCondition.java | 2 +- .../java/org/dspace/content/logic/condition/Condition.java | 2 +- .../dspace/content/logic/condition/InCollectionCondition.java | 2 +- .../dspace/content/logic/condition/InCommunityCondition.java | 2 +- .../dspace/content/logic/condition/IsWithdrawnCondition.java | 2 +- .../content/logic/condition/MetadataValueMatchCondition.java | 2 +- .../content/logic/condition/MetadataValuesMatchCondition.java | 2 +- .../content/logic/condition/ReadableByGroupCondition.java | 2 +- .../org/dspace/content/logic/operator/AbstractOperator.java | 2 +- .../src/main/java/org/dspace/content/logic/operator/And.java | 2 +- .../src/main/java/org/dspace/content/logic/operator/Nand.java | 2 +- .../src/main/java/org/dspace/content/logic/operator/Nor.java | 2 +- .../src/main/java/org/dspace/content/logic/operator/Not.java | 2 +- .../src/main/java/org/dspace/content/logic/operator/Or.java | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) 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 eb4c4cff35..c0649e9ea2 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 @@ -40,7 +40,7 @@ public class DefaultFilter implements Filter { * @return boolean * @throws LogicalStatementException */ - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { return this.statement.getResult(context, item); } } 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 e156529651..bfacca837b 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 @@ -31,5 +31,5 @@ public interface Filter extends LogicalStatement { * @return boolean * @throws LogicalStatementException */ - Boolean getResult(Context context, Item item) throws LogicalStatementException; + boolean getResult(Context context, Item item) throws LogicalStatementException; } 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 38c1e298ee..5fc3e76cd5 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 @@ -27,5 +27,5 @@ public interface LogicalStatement { * @return boolean result of evaluation * @throws LogicalStatementException */ - Boolean getResult(Context context, Item item) throws LogicalStatementException; + boolean getResult(Context context, Item item) throws LogicalStatementException; } 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 814c608a5e..b78de7f190 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 @@ -110,7 +110,7 @@ public class TestLogicRunner { DSpaceObject dso = handleService.resolveToObject(c, handle); if (Constants.typeText[dso.getType()].equals("ITEM")) { Item item = (Item) dso; - System.out.println(filter.getResult(c, item).toString()); + System.out.println(filter.getResult(c, item)); } else { System.out.println(handle + " is not an ITEM"); } @@ -127,7 +127,7 @@ public class TestLogicRunner { System.out.println( "Testing '" + filter + "' on item " + i.getHandle() + " ('" + i.getName() + "')" ); - System.out.println(filter.getResult(c, i).toString()); + System.out.println(filter.getResult(c, i)); } } catch (SQLException | LogicalStatementException e) { 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 255cbc7906..7a87e13066 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 @@ -73,7 +73,7 @@ public abstract class AbstractCondition implements Condition { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { if (item == null) { log.error("Error evaluating item. Passed item is null, returning false"); return false; 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 dcb57c389e..635f0997d3 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 @@ -30,7 +30,7 @@ public class BitstreamCountCondition extends AbstractCondition { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { // This super call just throws some useful exceptions if required objects are null super.getResult(context, item); 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 c097263b03..325964efdb 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 @@ -48,7 +48,7 @@ public interface Condition extends LogicalStatement { * @return boolean * @throws LogicalStatementException */ - Boolean getResult(Context context, Item item) throws LogicalStatementException; + boolean getResult(Context context, Item item) throws LogicalStatementException; public void setItemService(ItemService itemService); 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 35f4fd2815..0aaa1bff1d 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 @@ -37,7 +37,7 @@ public class InCollectionCondition extends AbstractCondition { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { List collectionHandles = (List)getParameters().get("collections"); 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 b2a90ee325..b9c1d15d2a 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 @@ -37,7 +37,7 @@ public class InCommunityCondition extends AbstractCondition { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { List communityHandles = (List)getParameters().get("communities"); List itemCollections = item.getCollections(); 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 ea3961744b..6475ef09e2 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 @@ -30,7 +30,7 @@ public class IsWithdrawnCondition extends AbstractCondition { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { log.debug("Result of isWithdrawn is " + item.isWithdrawn()); return item.isWithdrawn(); } 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 d39560c7a4..d9c774485a 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 @@ -37,7 +37,7 @@ public class MetadataValueMatchCondition extends AbstractCondition { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { String field = (String)getParameters().get("field"); if (field == null) { return false; 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 1fd30c4e88..df9cbfbf1d 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 @@ -37,7 +37,7 @@ public class MetadataValuesMatchCondition extends AbstractCondition { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { String field = (String)getParameters().get("field"); if (field == null) { return false; 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 ae61bce2a6..e76772803c 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 @@ -40,7 +40,7 @@ public class ReadableByGroupCondition extends AbstractCondition { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { String group = (String)getParameters().get("group"); String action = (String)getParameters().get("action"); 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 826eb99a88..99ece622f7 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 @@ -65,7 +65,7 @@ public abstract class AbstractOperator implements LogicalStatement { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { return false; } } 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 cdc4b8b86d..26606f2099 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 @@ -47,7 +47,7 @@ public class And extends AbstractOperator { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { for (LogicalStatement statement : getStatements()) { if (!statement.getResult(context, item)) { 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 8d1cec727e..1021ec6722 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 @@ -46,7 +46,7 @@ public class Nand extends AbstractOperator { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { return !(new And(getStatements()).getResult(context, item)); } } diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java index bc2de9e502..d28ac7578d 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java @@ -46,7 +46,7 @@ public class Nor extends AbstractOperator { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { return !(new Or(getStatements()).getResult(context, item)); } } 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 8d6a076c30..35c7bb22a7 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 @@ -63,7 +63,7 @@ public class Not implements LogicalStatement { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { return !statement.getResult(context, item); } } 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 7943f09410..5110ac31ba 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 @@ -47,7 +47,7 @@ public class Or extends AbstractOperator { * @throws LogicalStatementException */ @Override - public Boolean getResult(Context context, Item item) throws LogicalStatementException { + public boolean getResult(Context context, Item item) throws LogicalStatementException { for (LogicalStatement statement : getStatements()) { if (statement.getResult(context, item)) { From 3d609faf07366f05f4acba2d4372911c1846631a Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 23 Mar 2021 16:17:47 +1300 Subject: [PATCH 0030/1254] [DS-4522] Use more generic IdentifierNotApplicableException where applicable --- .../identifier/DOIIdentifierProvider.java | 4 +-- .../IdentifierNotApplicableException.java | 34 +++++++++++++++++++ .../identifier/IdentifierServiceImpl.java | 16 ++++----- .../VersionedDOIIdentifierProvider.java | 2 +- .../DOIIdentifierNotApplicableException.java | 4 ++- .../dspace/identifier/doi/DOIOrganiser.java | 3 +- 6 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/identifier/IdentifierNotApplicableException.java 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 1708535c27..3ffbde93e4 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -891,7 +891,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * DOI is registered for another object already. */ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier) - throws SQLException, DOIIdentifierException { + throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { return loadOrCreateDOI(context, dso, doiIdentifier, false); } @@ -911,7 +911,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @throws DOIIdentifierException */ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, Boolean skipFilter) - throws SQLException, DOIIdentifierException { + throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { DOI doi = null; diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierNotApplicableException.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierNotApplicableException.java new file mode 100644 index 0000000000..6708dc8013 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierNotApplicableException.java @@ -0,0 +1,34 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.identifier; + +/** + * + * Thrown when an identifier should not be applied to an item, eg. when it has been filtered by an item filter + * + * + * @author Kim Shepherd + */ +public class IdentifierNotApplicableException extends IdentifierException { + + public IdentifierNotApplicableException() { + super(); + } + + public IdentifierNotApplicableException(String message) { + super(message); + } + + public IdentifierNotApplicableException(String message, Throwable cause) { + super(message, cause); + } + + public IdentifierNotApplicableException(Throwable cause) { + super(cause); + } +} 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 4c889fac89..a0e0f125c1 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -72,8 +72,8 @@ public class IdentifierServiceImpl implements IdentifierService { if (!StringUtils.isEmpty(identifier)) { service.reserve(context, dso, identifier); } - } catch (DOIIdentifierNotApplicableException e) { - log.warn("DOI Identifier not reserved (inapplicable): " + e.getMessage()); + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not reserved (inapplicable): " + e.getMessage()); } } //Update our item @@ -88,8 +88,8 @@ public class IdentifierServiceImpl implements IdentifierService { if (service.supports(identifier)) { try { service.reserve(context, dso, identifier); - } catch (DOIIdentifierNotApplicableException e) { - log.warn("DOI Identifier not reserved (inapplicable): " + e.getMessage()); + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not reserved (inapplicable): " + e.getMessage()); } } } @@ -105,8 +105,8 @@ public class IdentifierServiceImpl implements IdentifierService { for (IdentifierProvider service : providers) { try { service.register(context, dso); - } catch (DOIIdentifierNotApplicableException e) { - log.warn("DOI Identifier not registered (inapplicable): " + e.getMessage()); + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not registered (inapplicable): " + e.getMessage()); } } //Update our item / collection / community @@ -124,8 +124,8 @@ public class IdentifierServiceImpl implements IdentifierService { try { service.register(context, object, identifier); registered = true; - } catch (DOIIdentifierNotApplicableException e) { - log.warn("DOI Identifier not registered (inapplicable): " + e.getMessage()); + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not registered (inapplicable): " + e.getMessage()); } } } 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 5464a0216e..cc43bd21b5 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java @@ -222,7 +222,7 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { // Should never return null! protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history) - throws AuthorizeException, SQLException, DOIIdentifierException { + 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. diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java index ab65e69db5..fa8138a26d 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIIdentifierNotApplicableException.java @@ -7,6 +7,8 @@ */ package org.dspace.identifier.doi; +import org.dspace.identifier.IdentifierNotApplicableException; + /** * * Thrown when an identifier should not be applied to an item, eg. when it has been filtered by an item filter @@ -14,7 +16,7 @@ package org.dspace.identifier.doi; * * @author Kim Shepherd */ -public class DOIIdentifierNotApplicableException extends DOIIdentifierException { +public class DOIIdentifierNotApplicableException extends IdentifierNotApplicableException { public DOIIdentifierNotApplicableException() { super(); 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 2ce7401a28..79308428d1 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 @@ -40,6 +40,7 @@ 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.IdentifierNotApplicableException; import org.dspace.identifier.factory.IdentifierServiceFactory; import org.dspace.identifier.service.DOIService; import org.dspace.services.ConfigurationService; @@ -243,8 +244,6 @@ public class DOIOrganiser { } catch (SQLException ex) { System.err.println("Error in database connection:" + ex.getMessage()); ex.printStackTrace(System.err); - } catch (DOIIdentifierNotApplicableException e) { - System.err.println("DOI not registered: " + e.getMessage()); } catch (DOIIdentifierException ex) { System.err.println("Error registering DOI identifier:" + ex.getMessage()); } From 713d91eaa2c923b86848dccf549d77f580e60096 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 24 Mar 2021 13:28:10 +1300 Subject: [PATCH 0031/1254] [DS-4522] Clean up unused imports --- .../main/java/org/dspace/identifier/IdentifierServiceImpl.java | 1 - .../src/main/java/org/dspace/identifier/doi/DOIOrganiser.java | 1 - 2 files changed, 2 deletions(-) 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 a0e0f125c1..b3375d66dc 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.core.Context; import org.dspace.handle.service.HandleService; -import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; import org.dspace.identifier.service.IdentifierService; import org.springframework.beans.factory.annotation.Autowired; 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 79308428d1..a583281abb 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 @@ -40,7 +40,6 @@ 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.IdentifierNotApplicableException; import org.dspace.identifier.factory.IdentifierServiceFactory; import org.dspace.identifier.service.DOIService; import org.dspace.services.ConfigurationService; From 5c47af057808c91d39b849d03eae1fc444e263eb Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:20:06 -0700 Subject: [PATCH 0032/1254] Adding iiif-apis dependency --- LICENSES_THIRD_PARTY | 1 + dspace-server-webapp/pom.xml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 69aa0f74bd..5307f8ab0c 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -395,6 +395,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.14 - http://www.slf4j.org) * SLF4J API Module (org.slf4j:slf4j-api:1.7.14 - http://www.slf4j.org) * SLF4J LOG4J-12 Binding (org.slf4j:slf4j-log4j12:1.7.14 - http://www.slf4j.org) + * iiif-apis (de.digitalcollections.iiif:iiif-apis:0.3.9 - https://github.com/dbmdz/iiif-apis) Mozilla Public License: diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 5e17518a5e..f4ea1b299c 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -474,6 +474,37 @@ + + de.digitalcollections.iiif + iiif-apis + 0.3.7 + + + org.reflections + reflections + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.springframework.security + spring-security-core + + + org.dmfs + iterators + + + com.fasterxml.jackson.module + jackson-module-parameter-names + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.apache.solr solr-cell From 5ac85abc6726c5bd9b6637e31fc0a7b1881c5681 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:44:02 -0700 Subject: [PATCH 0033/1254] Added iiif model generator classes. --- .../model/generator/AnnotationGenerator.java | 91 ++++++++ .../generator/AnnotationListGenerator.java | 43 ++++ .../model/generator/BehaviorGenerator.java | 35 +++ .../iiif/model/generator/CanvasGenerator.java | 118 ++++++++++ .../model/generator/CanvasItemsGenerator.java | 78 +++++++ .../generator/ContentAsTextGenerator.java | 29 +++ .../generator/ContentSearchGenerator.java | 73 ++++++ .../generator/ExternalLinksGenerator.java | 93 ++++++++ .../iiif/model/generator/IIIFResource.java | 20 ++ .../iiif/model/generator/IIIFService.java | 19 ++ .../rest/iiif/model/generator/IIIFValue.java | 17 ++ .../generator/ImageContentGenerator.java | 74 ++++++ .../generator/ImageServiceGenerator.java | 56 +++++ .../model/generator/ManifestGenerator.java | 217 ++++++++++++++++++ .../generator/MetadataEntryGenerator.java | 31 +++ .../model/generator/ProfileGenerator.java | 38 +++ .../generator/PropertyValueGenerator.java | 35 +++ .../iiif/model/generator/RangeGenerator.java | 73 ++++++ .../generator/SearchResultGenerator.java | 43 ++++ 19 files changed, 1183 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java new file mode 100644 index 0000000000..8e5887d335 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.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.app.rest.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.Motivation; +import de.digitalcollections.iiif.model.openannotation.Annotation; +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the IIIF Presentation API version 2.2.1 domain model. Annotations associate + * content resources and commentary with a canvas. + * + * This facade is provided for serializing AnnotationList and Search query responses. + */ +@Component +@RequestScope +public class AnnotationGenerator implements IIIFResource { + + private String identifier; + private Canvas onCanvas; + private Motivation motivation; + private ContentAsTextGenerator contentAsText; + private ExternalLinksGenerator otherContent; + // Renamed to "partOf" in IIIF v 3.0 + private List within; + + public static final Motivation PAINTING = new Motivation("sc:painting"); + public static final Motivation COMMENTING = new Motivation("oa:commenting"); + public static final Motivation LINKING = new Motivation("oa:linking"); + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public void setMotivation(Motivation motivation) { + this.motivation = motivation; + } + + public void setOnCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + this.onCanvas = (Canvas) canvas.getResource(); + } + + public void setResource(org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator contentAsText) { + this.contentAsText = contentAsText; + } + + public void setResource(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { + this.otherContent = otherContent; + } + + public void setWithin(List within) { + this.within = within; + } + + @Override + public Resource getResource() { + Annotation annotation = new Annotation(identifier, motivation); + if (contentAsText != null) { + annotation.setResource(contentAsText.getResource()); + } + if (otherContent != null) { + annotation.setResource(otherContent.getResource()); + } + if (onCanvas != null) { + annotation.setOn(onCanvas); + } + if (within != null) { + List manifests = new ArrayList<>(); + for (ManifestGenerator manifest : within) { + manifests.add(manifest.getResource()); + } + annotation.setWithin(manifests); + } + within = null; + identifier = null; + otherContent = null; + onCanvas = null; + return annotation; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java new file mode 100644 index 0000000000..4c76b4131c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java @@ -0,0 +1,43 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.model.generator; + +import de.digitalcollections.iiif.model.openannotation.Annotation; +import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * An ordered list of annotations. Annotation Lists are separate resources that + * should be dereferenced when encountered. + * + * This class is used when retrieving an AnnotationList referenced in the Manifest. + */ +@Component +@Scope("prototype") +public class AnnotationListGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { + + private String identifier; + private Annotation annotation; + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public void addResource(org.dspace.app.rest.iiif.model.generator.AnnotationGenerator annotation) { + this.annotation = (Annotation) annotation.getResource(); + } + + @Override + public Resource getResource() { + AnnotationList annotations = new AnnotationList(identifier); + annotations.addResource(annotation); + return annotations; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java new file mode 100644 index 0000000000..7ea76e8026 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.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.iiif.model.generator; + +import de.digitalcollections.iiif.model.enums.ViewingHint; +import org.springframework.stereotype.Component; + +/** + * IIIF Presentation API 2.1.1 ViewingHint is a hint to the client as to the most appropriate method of + * displaying the resource. This class wraps the ViewingHint enum from the domain model. + * + * With IIIF Presentation API 3.0 the viewingHint property is renamed to "behavior". + */ +@Component +public class BehaviorGenerator implements IIIFValue { + + private String type; + + public void setType(String type) { + this.type = type; + } + + @Override + public ViewingHint getValue() { + if (type == null) { + throw new RuntimeException("Type must be provided for viewing hint."); + } + return new ViewingHint(type); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java new file mode 100644 index 0000000000..f4f611f21d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.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.app.rest.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.ImageContent; +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Facade for Presentation API version 2.1.1 Canvas model. + * + * Changes a Presentation API version 3.0 will likely require new fields for + * this class to support multiple media types. + */ +@Component +@Scope("prototype") +public class CanvasGenerator implements IIIFResource { + + String identifier; + String label; + Integer height; + Integer width; + List> imageContent = new ArrayList<>(); + Resource thumbContent; + + /** + * Canvases must be identified by a URI and it must be an HTTP(s) URI. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Every canvas must have a label to display. + * @param label + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Every canvas must have an integer height. + * @param height + */ + public void setHeight(int height) { + this.height = height; + } + + /** + * Every canvas must have an integer width. + * @param width + */ + public void setWidth(int width) { + this.width = width; + } + + /** + * The ImageContent resource to be assigned to the canvas. + * @param imageContent + */ + public void addImage(Resource imageContent) { + this.imageContent.add(imageContent); + } + + /** + * The ImageContent resource to be assigned as the thumbnail the canvas. + * @param thumbnail + */ + public void addThumbnail(ImageContentGenerator thumbnail) { + this.thumbContent = thumbnail.getResource(); + } + + @Override + public Resource getResource() { + /** + * The Canvas resource typically includes image content. + */ + Canvas canvas; + if (identifier == null) { + throw new RuntimeException("The Canvas resource requires an identifier."); + } + if (label != null) { + canvas = new Canvas(identifier, label); + } else { + canvas = new Canvas(identifier); + } + if (imageContent.size() > 0) { + if (height == null || width == null) { + throw new RuntimeException("The Canvas resource requires both height and width dimensions."); + } + canvas.setWidth(width); + canvas.setHeight(height); + for (Resource res : imageContent) { + canvas.addImage((ImageContent) res); + } + if (thumbContent != null) { + canvas.addThumbnail((ImageContent) thumbContent); + } + } + // Reset properties after each use. + identifier = null; + imageContent.clear(); + label = null; + + return canvas; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java new file mode 100644 index 0000000000..a31d487f63 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.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.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.OtherContent; +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import de.digitalcollections.iiif.model.sharedcanvas.Sequence; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the current Presentation API version 2.1.1 domain model's Sequence class. + * + * In Presentation API version 2.1.1, each Manifest includes a single Sequence that defines + * the order of the views of the object. + * + * Sequence is removed with Presentation API version 3.0. Canvases are added to the Manifest + * items property instead. + */ +@Component +@RequestScope +public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { + + private String identifier; + private OtherContent rendering; + private final List canvas = new ArrayList<>(); + + @Autowired + org.dspace.app.rest.iiif.model.generator.BehaviorGenerator viewingHintFascade; + + /** + * Mandatory. The domain model requires a URI identifier for the sequence. + * @param identifier string for the URI + */ + public void setIdentifier(String identifier) { + + this.identifier = identifier; + } + + /** + * A link to an external resource intended for display or download by a human user. + * This is typically going to be a PDF file. + * @param otherContent wrapper for OtherContent + */ + public void addRendering(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { + + this.rendering = (OtherContent) otherContent.getResource(); + } + + /** + * Add a Canvas to the sequence. + * @param canvas wrapper for Canvas + */ + public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + + this.canvas.add((Canvas) canvas.getResource()); + } + + @Override + public Resource getResource() { + Sequence items = new Sequence(identifier); + if (rendering != null) { + items.addRendering(rendering); + } + items.setCanvases(canvas); + return items; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java new file mode 100644 index 0000000000..0c5c9039d0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import de.digitalcollections.iiif.model.openannotation.ContentAsText; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.stereotype.Component; + +@Component +public class ContentAsTextGenerator implements IIIFResource { + + private String text; + + public void setText(String text) { + this.text = text; + } + @Override + public Resource getResource() { + if (text == null) { + throw new RuntimeException("ContextAsText requires text input."); + } + return new ContentAsText(text); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java new file mode 100644 index 0000000000..9876ff0bd4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.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.iiif.model.generator; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import de.digitalcollections.iiif.model.Profile; +import de.digitalcollections.iiif.model.Service; +import de.digitalcollections.iiif.model.search.ContentSearchService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the Search API version 1.0 search service description. + * + * Added to the Manifest of items that support full-text searching, identified + * by the "relationship.type: IIIFSearchable" DSpace metadata field. + */ +@Component +@RequestScope +public class ContentSearchGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFService { + + private String identifier; + private String label; + + @Autowired + org.dspace.app.rest.iiif.model.generator.ProfileGenerator profile; + + /** + * Mandatory URI for search service. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Optional label + * @param label + */ + public void setLabel(String label) { + this.label = label; + } + + @Override + public Service getService() { + if (identifier == null) { + throw new RuntimeException("You must provide an identifier for the search service."); + } + ContentSearchService contentSearchService = new ContentSearchService(identifier); + if (label != null) { + contentSearchService.setLabel(label); + } + try { + contentSearchService.setContext(new URI("http://iiif.io/api/search/0/context.json")); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + ArrayList profiles = new ArrayList<>(); + profile.setIdentifier("http://iiif.io/api/search/0/search"); + profiles.add(profile.getValue()); + contentSearchService.setProfiles(profiles); + return contentSearchService; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java new file mode 100644 index 0000000000..0ad003b12b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.model.generator; + +import de.digitalcollections.iiif.model.OtherContent; +import de.digitalcollections.iiif.model.PropertyValue; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Facade for the IIIF Presentation API version 2.1.1 "OtherContent" domain model class. + * + * This is the type for Content resources such as images or texts that are associated with a canvas. + * Used in the "related", "renderings" and "otherContent" fields of IIIF resources. + * + * IIIF Presentation API version 3.0 removes the otherContent property and uses annotations + * and items instead. + */ +@Component +public class ExternalLinksGenerator implements IIIFResource { + + @Autowired + PropertyValueGenerator propertyValue; + + private String identifier; + private String format; + private PropertyValue label; + private String type; + + /** + * Sets the mandatory identifier. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the optional format. + * @param format + */ + public void setFormat(String format) { + this.format = format; + } + + /** + * Sets the optional label. + * @param label + */ + public void setLabel(String label) { + propertyValue.setPropertyValue(label); + this.label = propertyValue.getValue(); + } + + /** + * Sets the optional type. + * @param type + */ + public void setType(String type) { + this.type = type; + } + + @Override + public Resource getResource() { + OtherContent otherContent; + if (format != null) { + otherContent = new OtherContent(identifier, format); + } else { + otherContent = new OtherContent(identifier); + } + if (label != null) { + otherContent.setLabel(label); + } + if (type != null) { + otherContent.setType(type); + } + + // Reset facade properties after creating the resource. + identifier = null; + format = null; + label = null; + type = null; + + return otherContent; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java new file mode 100644 index 0000000000..c752e5f866 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java @@ -0,0 +1,20 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import de.digitalcollections.iiif.model.sharedcanvas.Resource; + +public interface IIIFResource { + + /** + * Returns a Resource for serialization. + * @return + */ + Resource getResource(); + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java new file mode 100644 index 0000000000..24d70de119 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java @@ -0,0 +1,19 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import de.digitalcollections.iiif.model.Service; + +public interface IIIFService { + + /** + * Returns a Service for serialization. + * @return + */ + Service getService(); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java new file mode 100644 index 0000000000..2e0cc120bd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.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.app.rest.iiif.model.generator; + +public interface IIIFValue { + + /** + * Returns a value object. + * @return + */ + Object getValue(); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java new file mode 100644 index 0000000000..d8ab207dd4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.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.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.ImageContent; +import de.digitalcollections.iiif.model.Service; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the domain model's ImageContent. + * + * Presentation API version 2.1.1: The ImageContent entity is contained in the "resource" + * field of annotations with motivation "sc:painting". Image resources, and only image resources, + * are included in the images property of the canvas. This changes in API version 3.0. + */ +@Component +@RequestScope +public class ImageContentGenerator implements IIIFResource { + + private String identifier; + private String mimetype; + private org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator imageService; + + /** + * Sets the mandatory identifier for image content. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the optional mimetype. + * @param mimetype + */ + public void setFormat(String mimetype) { + this.mimetype = mimetype; + } + + /** + * Sets the image service that the client will use to retrieve images. + * @param imageService + */ + public void addService(org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator imageService) { + this.imageService = imageService; + } + + @Override + public Resource getResource() { + if (identifier == null) { + throw new RuntimeException("The ImageContent resource requires an identifier."); + } + ImageContent imageContent = new ImageContent(identifier); + if (mimetype != null) { + imageContent.setFormat(mimetype); + } + // Supporting a single service for each image resource. + List services = new ArrayList<>(); + services.add(imageService.getService()); + imageContent.setServices(services); + + return imageContent; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java new file mode 100644 index 0000000000..cac56d0044 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java @@ -0,0 +1,56 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import java.util.ArrayList; + +import de.digitalcollections.iiif.model.Profile; +import de.digitalcollections.iiif.model.Service; +import de.digitalcollections.iiif.model.image.ImageService; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade or Presentation API version 2.1.1 image service property that is added to + * each image resource. + */ +@Component +@RequestScope +public class ImageServiceGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFService { + + private String identifier; + private Profile profile; + + /** + * Sets the mandatory identifier for the image service. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the IIIF image profile. + * @param profile + */ + public void setProfile(org.dspace.app.rest.iiif.model.generator.ProfileGenerator profile) { + this.profile = profile.getValue(); + + } + + @Override + public Service getService() { + ImageService imageService = new ImageService(identifier); + if (profile != null) { + ArrayList profiles = new ArrayList<>(); + profiles.add(profile); + imageService.setProfiles(profiles); + } + return imageService; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java new file mode 100644 index 0000000000..9022a7c960 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -0,0 +1,217 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.model.generator; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.ImageContent; +import de.digitalcollections.iiif.model.MetadataEntry; +import de.digitalcollections.iiif.model.OtherContent; +import de.digitalcollections.iiif.model.PropertyValue; +import de.digitalcollections.iiif.model.enums.ViewingHint; +import de.digitalcollections.iiif.model.search.ContentSearchService; +import de.digitalcollections.iiif.model.sharedcanvas.Manifest; +import de.digitalcollections.iiif.model.sharedcanvas.Range; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import de.digitalcollections.iiif.model.sharedcanvas.Sequence; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the IIIF Presentation API version 2.2.1 domain model. + * + * The Manifest is an overall description of the structure and properties of the digital representation + * of an object. It carries information needed for the viewer to present the digitized content to the user, + * such as a title and other descriptive information about the object or the intellectual work that + * it conveys. Each manifest describes how to present a single object such as a book, a photograph, + * or a statue. + */ +@Component +@RequestScope +public class ManifestGenerator implements IIIFResource { + + private String identifier; + private String label; + private OtherContent seeAlso; + // Becomes the "items" element in IIIF version 3.0 + private CanvasItemsGenerator sequence; + // Renamed to "homepage" in IIIF version 3.0 + private OtherContent related; + // Renamed to "behavior" with IIIF version 3.0 + private ViewingHint viewingHint; + private Resource thumbnail; + private ContentSearchService searchService; + private PropertyValue description; + private final List metadata = new ArrayList<>(); + // Renamed to "rights" with IIIF version 3.0 + private final List license = new ArrayList<>(); + private List ranges = new ArrayList<>(); + + @Autowired + org.dspace.app.rest.iiif.model.generator.PropertyValueGenerator propertyValue; + + @Autowired + org.dspace.app.rest.iiif.model.generator.MetadataEntryGenerator metadataEntry; + + @Autowired + BehaviorGenerator behaviorFascade; + + /** + * Sets the mandatory Manifest ID. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the manditory Manifest label. + * @param label + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Sets the behavior. A hint to the client as to the most appropriate method of displaying the resource + * In IIIF Presentation API version 3.0 semantics this is the "behavior" + * @param viewingHint + */ + public void addViewingHint(String viewingHint) { + behaviorFascade.setType(viewingHint); + this.viewingHint = behaviorFascade.getValue(); + } + + /** + * Use to add single mandatory sequence to Manifest. In IIIF Presentation API 3.0 "sequence" + * is replaced by "items" + * @param sequence + */ + public void addSequence(CanvasItemsGenerator sequence) { + this.sequence = sequence; + } + + /** + * Add otional seeAlso element to Manifest. + * @param seeAlso + */ + public void addSeeAlso(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator seeAlso) { + this.seeAlso = (OtherContent) seeAlso.getResource(); + } + + /** + * Add optional thumbnail image to manifest. + * @param thumbnail + */ + public void addThumbnail(ImageContentGenerator thumbnail) { + this.thumbnail = thumbnail.getResource(); + } + + /** + * Add optional related element to Manifest. + * @param related + */ + public void addRelated(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator related) { + this.related = (OtherContent) related.getResource(); + } + + /** + * Adds optional search service to Manifest. + * @param searchService + */ + public void addService(org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator searchService) { + this.searchService = (ContentSearchService) searchService.getService(); + } + + /** + * Adds single metadata field to Manifest. + * @param field + * @param value + */ + public void addMetadata(String field, String value) { + metadataEntry.setField(field); + metadataEntry.setValue(value); + metadata.add(metadataEntry.getValue()); + } + + /** + * Adds optional license to Manifest. + * @param license + */ + public void addLicense(String license) { + this.license.add(URI.create(license)); + } + + /** + * Adds optional description to Manifest. + * @param field + * @param value + */ + public void addDescription(String field, String value) { + propertyValue.setPropertyValue(field, value); + description = propertyValue.getValue(); + } + + /** + * Adds optional Range to the manifest's structures element. + * @param range + */ + public void addRange(RangeGenerator range) { + ranges.add((Range) range.getResource()); + } + + @Override + public Resource getResource() { + if (identifier == null) { + throw new RuntimeException("The Manifest resource requires an identifier."); + } + Manifest manifest; + if (label != null) { + manifest = new Manifest(identifier, label); + } else { + manifest = new Manifest(identifier); + } + if (sequence != null) { + manifest.addSequence((Sequence) sequence.getResource()); + } + if (ranges.size() > 0) { + manifest.setRanges(ranges); + } + if (metadata.size() > 0) { + for (MetadataEntry meta : metadata) { + manifest.addMetadata(meta); + } + } + if (seeAlso != null) { + manifest.addSeeAlso(seeAlso); + } + if (related != null) { + manifest.addRelated(related); + } + if (searchService != null) { + manifest.addService(searchService); + } + if (license.size() > 0) { + manifest.setLicenses(license); + } + if (description != null) { + manifest.setDescription(description); + } + if (thumbnail != null) { + manifest.addThumbnail((ImageContent) thumbnail); + } + if (viewingHint != null) { + manifest.addViewingHint(viewingHint); + } + return manifest; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java new file mode 100644 index 0000000000..57dccadfa7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.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.app.rest.iiif.model.generator; + +import de.digitalcollections.iiif.model.MetadataEntry; +import org.springframework.stereotype.Component; + +@Component +public class MetadataEntryGenerator implements IIIFValue { + + private String field; + private String value; + + public void setField(String field) { + this.field = field; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public MetadataEntry getValue() { + return new MetadataEntry(field, value); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java new file mode 100644 index 0000000000..6b7e079d0b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import java.net.URI; +import java.net.URISyntaxException; + +import de.digitalcollections.iiif.model.Profile; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class ProfileGenerator implements IIIFValue { + + private String identifier; + /** + * Input String will be converted to URI for use in the Profile. + * @param identifier URI as string + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + @Override + public Profile getValue() { + try { + return new Profile(new URI(identifier)); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java new file mode 100644 index 0000000000..af90628dc1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.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.iiif.model.generator; + +import de.digitalcollections.iiif.model.PropertyValue; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Supports single value PropertyValues. + */ +@Component +@Scope("prototype") +public class PropertyValueGenerator implements IIIFValue { + + private PropertyValue propertyValue; + + public void setPropertyValue(String label) { + propertyValue = new PropertyValue(label); + } + + public void setPropertyValue(String val1, String val2) { + propertyValue = new PropertyValue(val1, val2); + } + + @Override + public PropertyValue getValue() { + return propertyValue; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java new file mode 100644 index 0000000000..4c8c568953 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.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.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Range; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * In Presentation API version 2.1.1, adding a range to the manifest allows the client to display a structured + * hierarchy to enable the user to navigate within the object without merely stepping through the current sequence. + * The rationale for separating ranges from sequences is that there is likely to be overlap between different ranges, + * such as the physical structure of a book compared to the textual structure of the work. + * + * This is used to populate the "structures" element of the Manifest. (The REST API service looks to the "info.json" + * file for ranges.) + */ +@Component +@Scope("prototype") +public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { + + private String identifier; + private String label; + private final List canvasList = new ArrayList<>(); + + /** + * Sets mandatory range identifier. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets mandatory range label. + * @param label + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Adds canvas to Range canvas list. + * @param canvas + */ + public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + canvasList.add((Canvas) canvas.getResource()); + } + + @Override + public Resource getResource() { + Range range = new Range(identifier, label); + for (Canvas canvas : canvasList) { + range.addCanvas(canvas); + } + // Reset properties after each use + identifier = null; + canvasList.clear(); + label = null; + + return range; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java new file mode 100644 index 0000000000..289005ec7b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java @@ -0,0 +1,43 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.openannotation.Annotation; +import de.digitalcollections.iiif.model.search.SearchResult; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the AnnotationList that contains hits for a given search query. + */ +@Component +@RequestScope +public class SearchResultGenerator implements IIIFResource { + + private String identifier; + private final List annotations = new ArrayList<>(); + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public void addResource(AnnotationGenerator annotation) { + annotations.add((Annotation) annotation.getResource()); + } + + @Override + public Resource getResource() { + SearchResult searchResult = new SearchResult(identifier); + searchResult.setResources(annotations); + return searchResult; + } +} From dae544ac970fb280c637593c4e65b7595bffe0b0 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:47:15 -0700 Subject: [PATCH 0034/1254] Added factory for objectmapper. --- .../rest/iiif/model/ObjectMapperFactory.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java new file mode 100644 index 0000000000..1b5bb782c0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java @@ -0,0 +1,26 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import de.digitalcollections.iiif.model.jackson.IiifModule; +import de.digitalcollections.iiif.model.jackson.IiifObjectMapper; + +public class ObjectMapperFactory { + + private ObjectMapperFactory() {} + + public static ObjectMapper getIiifObjectMapper() { + return new IiifObjectMapper(); + } + + public static SimpleModule getIiifModule() { + return new IiifModule(); + } +} From 1a353d4bd5b2dad6a89cefcffe515f9bec1d00b0 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:48:31 -0700 Subject: [PATCH 0035/1254] Added classes for parsing the info.json object. --- .../rest/iiif/model/info/AnnotationModel.java | 30 +++++++++++ .../app/rest/iiif/model/info/CanvasModel.java | 50 ++++++++++++++++++ .../rest/iiif/model/info/GlobalDefaults.java | 51 +++++++++++++++++++ .../dspace/app/rest/iiif/model/info/Info.java | 42 +++++++++++++++ .../app/rest/iiif/model/info/RangeModel.java | 31 +++++++++++ 5 files changed, 204 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.java new file mode 100644 index 0000000000..3bfcf5efca --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.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.iiif.model.info; + +public class AnnotationModel { + + private String motivation; + private String id; + + public void setMotivation(String motivation) { + this.motivation = motivation; + } + + public String getMotivation() { + return motivation; + } + + public void setID(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.java new file mode 100644 index 0000000000..8dc6cac0ca --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.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.iiif.model.info; + +public class CanvasModel { + + private String label; + private int width; + private int height; + private int pos; + + public void setLabel(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getWidth() { + return width; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getHeight() { + return height; + } + + // TODO: These can be removed. + public void setPos(int pos) { + this.pos = pos; + } + + public int getPos() { + return pos; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java new file mode 100644 index 0000000000..b8455e6332 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.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.iiif.model.info; + +public class GlobalDefaults { + + private boolean activated; + private String label; + private int width; + private int height; + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java new file mode 100644 index 0000000000..898576639f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java @@ -0,0 +1,42 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.info; + +import java.util.List; + +public class Info { + + private List canvases; + private List structures; + private GlobalDefaults globalDefaults; + + public GlobalDefaults getGlobalDefaults() { + return globalDefaults; + } + + public void setGlobalDefaults(GlobalDefaults globalDefaults) { + this.globalDefaults = globalDefaults; + } + + public void setCanvases(List canvases) { + this.canvases = canvases; + } + + public List getCanvases() { + return this.canvases; + } + + public void setStructures(List structures) { + this.structures = structures; + } + + public List getStructures() { + return structures; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.java new file mode 100644 index 0000000000..b1ef0503bf --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.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.app.rest.iiif.model.info; + +public class RangeModel { + + private String label; + private int start; + + public void setLabel(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public void setStart(int start) { + this.start = start; + } + + public int getStart() { + return start; + } + +} From 1a6d3d4ee3e6572ad09217246239247c0213dade Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:52:21 -0700 Subject: [PATCH 0036/1254] Added iiif service and utility classes. --- .../iiif/service/AbstractResourceService.java | 157 ++++++++ .../iiif/service/AnnotationListService.java | 121 +++++++ .../iiif/service/CanvasLookupService.java | 59 +++ .../app/rest/iiif/service/CanvasService.java | 93 +++++ .../rest/iiif/service/ManifestService.java | 338 ++++++++++++++++++ .../app/rest/iiif/service/SearchService.java | 213 +++++++++++ .../app/rest/iiif/service/util/IIIFUtils.java | 314 ++++++++++++++++ .../iiif/service/util/ImageProfileUtil.java | 33 ++ .../iiif/service/util/ThumbProfileUtil.java | 34 ++ 9 files changed, 1362 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java new file mode 100644 index 0000000000..6b87cae231 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -0,0 +1,157 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.util.UUID; + +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator; +import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.rest.iiif.service.util.ImageProfileUtil; +import org.dspace.app.rest.iiif.service.util.ThumbProfileUtil; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Base class for IIIF responses. + */ +public abstract class AbstractResourceService { + + /** + * These values are defined in dspace configuration. + */ + protected String IIIF_ENDPOINT; + protected String IMAGE_SERVICE; + protected String SEARCH_URL; + protected String CLIENT_URL; + protected String BITSTREAM_PATH_PREFIX; + /** + * Possible values: "paged" or "individuals". Typically paged is preferred + * for documents. However, it can be overridden in configuration if necessary + * for the viewer client. + */ + protected static String DOCUMENT_VIEWING_HINT; + + // TODO: should these bundle settings be added to dspace configuration or hard-coded here? + // The DSpace bundle used for IIIF entity types. + protected static final String IIIF_BUNDLE = "IIIF"; + // The DSpace bundle for other content related to item. + protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; + + // Paths for IIIF Image API requests. + protected static final String THUMBNAIL_PATH = "/full/,90/0/default.jpg"; + protected static final String IMAGE_PATH = "/full/full/0/default.jpg"; + + @Autowired + IIIFUtils utils; + + @Autowired + ThumbProfileUtil thumbUtil; + + @Autowired + ImageProfileUtil imageUtil; + + @Autowired + ImageContentGenerator imageContent; + + @Autowired + ImageServiceGenerator imageService; + + /** + * Set constants using DSpace configuration definitions. + * @param configurationService the DSpace configuration service + */ + protected void setConfiguration(ConfigurationService configurationService) { + IIIF_ENDPOINT = configurationService.getProperty("iiif.url"); + IMAGE_SERVICE = configurationService.getProperty("iiif.image.server"); + SEARCH_URL = configurationService.getProperty("iiif.solr.search.url"); + BITSTREAM_PATH_PREFIX = configurationService.getProperty("iiif.bitstream.url"); + DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint"); + CLIENT_URL = configurationService.getProperty("dspace.ui.url"); + } + + /** + * Creates the manifest id from the provided uuid. + * @param uuid the item id + * @return the manifest identifier (url) + */ + protected String getManifestId(UUID uuid) { + return IIIF_ENDPOINT + uuid + "/manifest"; + } + + /** + * Association of images with their respective canvases is done via annotations. + * Only the annotations that associate images or parts of images are included in + * the canvas in the images property. If a IIIF Image API service is available for + * the image, then a link to the service’s base URI should be included. + * + * This method adds an image annotations to a canvas for both thumbnail and full size + * images. The annotation references the IIIF image service. + * + * @param canvas the Canvas object. + * @param mimeType the image mime type + * @param bitstreamID the bitstream uuid + */ + protected void addImage(CanvasGenerator canvas, String mimeType, UUID bitstreamID) throws + RuntimeException { + canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); + // Add image content resource to canvas facade. + canvas.addImage(getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH).getResource()); + } + + /** + * A small image that depicts or pictorially represents the resource that + * the property is attached to, such as the title page, a significant image + * or rendering of a canvas with multiple content resources associated with it. + * It is recommended that a IIIF Image API service be available for this image for + * manipulations such as resizing. + * + * This method returns a thumbnail annotation that includes the IIIF image service. + * + * @param uuid the bitstream id + * @return thumbnail Annotation + */ + protected ImageContentGenerator getThumbnailAnnotation(UUID uuid, String mimetype) throws + RuntimeException { + return getImageContent(uuid, mimetype, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); + } + + /** + * Association of images with their respective canvases is done via annotations. The Open Annotation model + * allows any resource to be associated with any other resource, or parts thereof, and it is reused for + * both commentary and painting resources on the canvas. + * @param uuid bitstream uuid + * @param mimetype bitstream mimetype + * @param profile the service profile + * @param path the path component of the identifier + * @return + */ + private ImageContentGenerator getImageContent(UUID uuid, String mimetype, ProfileGenerator profile, String path) { + imageContent.setFormat(mimetype); + imageContent.setIdentifier(IMAGE_SERVICE + uuid + path); + imageContent.addService(getImageService(profile, uuid.toString())); + return imageContent; + } + + /** + * A link to a service that makes more functionality available for the resource, + * such as from an image to the base URI of an associated IIIF Image API service. + * + * @param profile service profile + * @param uuid id of the image bitstream + * @return object representing the Image Service + */ + private ImageServiceGenerator getImageService(ProfileGenerator profile, String uuid) { + imageService.setIdentifier(IMAGE_SERVICE + uuid); + imageService.setProfile(profile); + return imageService; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java new file mode 100644 index 0000000000..43e7caf653 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java @@ -0,0 +1,121 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.rest.iiif.model.generator.AnnotationListGenerator; +import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.rest.iiif.model.generator.PropertyValueGenerator; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class AnnotationListService extends AbstractResourceService { + + @Autowired + IIIFUtils utils; + + @Autowired + ItemService itemService; + + @Autowired + BitstreamService bitstreamService; + + @Autowired + BitstreamFormatService bitstreamFormatService; + + @Autowired + PropertyValueGenerator propertyValue; + + @Autowired + AnnotationListGenerator annotationList; + + @Autowired + AnnotationGenerator annotation; + + @Autowired + ExternalLinksGenerator otherContent; + + public AnnotationListService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Returns an AnnotationList for bitstreams in the OtherContent bundle. + * These resources are not appended directly to the manifest but can be accessed + * via the seeAlso link. + * + * The semantics of this linking property may be extended to full text files, but + * machine readable formats like ALTO, METS, and schema.org descriptions are preferred. + * + * @param context DSpace context + * @param id bitstream uuid + * @return AnnotationList as JSON + */ + public String getSeeAlsoAnnotations(Context context, UUID id) + throws RuntimeException { + /** + * We need the DSpace item to proceed. + */ + Item item; + try { + item = itemService.find(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + /** + * AnnotationList requires an identifier. + */ + annotationList.setIdentifier(IIIF_ENDPOINT + id + "/manifest/seeAlso"); + /** + * Get the "OtherContent" bundle for the item. Then add + * Annotations for each bitstream found in the bundle. + */ + List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); + if (bundles.size() > 0) { + for (Bundle bundle : bundles) { + List bitstreams = bundle.getBitstreams(); + for (Bitstream bitstream : bitstreams) { + BitstreamFormat format; + String mimetype; + try { + format = bitstream.getFormat(context); + mimetype = format.getMIMEType(); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + annotation.setIdentifier(IIIF_ENDPOINT + bitstream.getID() + "/annot"); + annotation.setMotivation(AnnotationGenerator.LINKING); + otherContent.setIdentifier(BITSTREAM_PATH_PREFIX + + "/" + + bitstream.getID() + + "/content"); + otherContent.setFormat(mimetype); + otherContent.setLabel(bitstream.getName()); + annotation.setResource(otherContent); + annotationList.addResource(annotation); + } + } + } + return utils.asJson(annotationList.getResource()); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java new file mode 100644 index 0000000000..186a3de7cb --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.service; + +import java.util.ArrayList; +import java.util.UUID; + +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.stereotype.Component; + +/** + * Canvases may be dereferenced separately from the manifest via their URIs. + */ +@Component +public class CanvasLookupService extends AbstractResourceService { + + @Autowired + IIIFUtils utils; + + @Autowired + CanvasService canvasService; + + public CanvasLookupService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + public String generateCanvas(Context context, Item item, String canvasId) { + int canvasPosition = utils.getCanvasId(canvasId); + Bitstream bitstream = utils.getBitstreamForCanvas(item, IIIF_BUNDLE, canvasPosition); + if (bitstream == null) { + throw new ResourceNotFoundException(); + } + Info info = + utils.validateInfoForSingleCanvas(utils.getInfo(context, item, IIIF_BUNDLE), canvasPosition); + ArrayList bitstreams = new ArrayList<>(); + bitstreams.add(bitstream); + UUID bitstreamID = bitstream.getID(); + String mimeType = utils.getBitstreamMimeType(bitstream, context); + CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, canvasPosition); + if (mimeType.contains("image/")) { + addImage(canvas, mimeType, bitstreamID); + } + return utils.asJson(canvas.getResource()); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java new file mode 100644 index 0000000000..fe1f30427a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class CanvasService extends AbstractResourceService { + + private static final Logger log = Logger.getLogger(CanvasService.class); + + // Default canvas dimensions. + protected static final Integer DEFAULT_CANVAS_WIDTH = 1200; + protected static final Integer DEFAULT_CANVAS_HEIGHT = 1600; + + @Autowired + CanvasGenerator canvas; + + /** + * Constructor. + * @param configurationService the DSpace configuration service. + */ + public CanvasService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Creates a single Canvas object. If canvas parameters are provided by the + * Info object they are used. If canvas parameters are unavailable, default values + * are used instead. + * + * @param id manifest id + * @param info parameters for this canvas + * @param count the canvas position in the sequence. + * @return canvas object + */ + protected CanvasGenerator getCanvas(String id, Info info, int count) { + // Defaults settings. + int canvasWidth = DEFAULT_CANVAS_WIDTH; + int canvasHeight = DEFAULT_CANVAS_HEIGHT; + int pagePosition = count + 1; + String label = "Page " + pagePosition; + // Override with settings from info.json, if available. + if (info != null && info.getGlobalDefaults() != null && info.getCanvases() != null) { + // Use global settings if activated. + if (info.getGlobalDefaults().isActivated()) { + // Create unique label by appending position to the default label. + label = info.getGlobalDefaults().getLabel() + " " + pagePosition; + canvasWidth = info.getGlobalDefaults().getWidth(); + canvasHeight = info.getGlobalDefaults().getHeight(); + } else if (info.getCanvases().get(count) != null) { + if (info.getCanvases().get(count).getLabel().length() > 0) { + // Individually defined canvas labels assumed unique, and are not incremented. + label = info.getCanvases().get(count).getLabel(); + } + canvasWidth = info.getCanvases().get(count).getWidth(); + canvasHeight = info.getCanvases().get(count).getHeight(); + } + } else { + log.info("Correctly formatted info.json was not found for item. Using application defaults."); + } + canvas.setIdentifier(IIIF_ENDPOINT + id + "/canvas/c" + count); + canvas.setLabel(label); + canvas.setHeight(canvasHeight); + canvas.setWidth(canvasWidth); + return canvas; + } + + /** + * Ranges expect the Canvas object to have only an identifier. This method assures that the + * injected canvas facade is empty before setting the identifier. + * @param identifier the DSpace item identifier + * @param startCanvas the position of the canvas in list + * @return + */ + protected CanvasGenerator getRangeCanvasReference(String identifier, String startCanvas) { + canvas.setIdentifier(IIIF_ENDPOINT + identifier + startCanvas); + return canvas; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java new file mode 100644 index 0000000000..ec63cd750d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -0,0 +1,338 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; +import org.apache.log4j.Logger; +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; +import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; +import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; +import org.dspace.app.rest.iiif.model.generator.RangeGenerator; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.app.rest.iiif.model.info.RangeModel; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Generates IIIF Manifest JSON response for a DSpace Item. + */ +@Component +public class ManifestService extends AbstractResourceService { + + private static final Logger log = Logger.getLogger(ManifestService.class); + + private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; + private static final String RELATED_ITEM_LABEL = "DSpace item view"; + private static final String SEE_ALSO_LABEL = "More descriptions of this resource"; + + @Autowired + protected ItemService itemService; + + @Autowired + CanvasService canvasService; + + @Autowired + ExternalLinksGenerator otherContentGenerator; + + @Autowired + ManifestGenerator manifestGenerator; + + @Autowired + CanvasItemsGenerator sequenceGenerator; + + @Autowired + RangeGenerator rangeGenerator; + + @Autowired + ContentSearchGenerator contentSearchGenerator; + + @Autowired + IIIFUtils utils; + + /** + * Constructor. + * @param configurationService the DSpace configuration service. + */ + public ManifestService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Returns serialized Manifest response for a DSpace item. + * + * @param item the DSpace Item + * @param context the DSpace context + * @return Manifest as JSON + */ + public String getManifest(Item item, Context context) { + initializeManifestGenerator(item, context); + return utils.asJson(manifestGenerator.getResource()); + } + + /** + * Initializes the Manifest for a DSpace item. + * + * @param item DSpace Item + * @param context DSpace context + * @return manifest object + */ + private void initializeManifestGenerator(Item item, Context context) { + List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); + List bitstreams = utils.getBitstreams(bundles); + Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); + manifestGenerator.setIdentifier(getManifestId(item.getID())); + manifestGenerator.setLabel(item.getName()); + addRelated(item); + addSearchService(item); + addMetadata(item.getMetadata()); + addViewingHint(bitstreams.size()); + addThumbnail(bitstreams, context); + addSequence(item, bitstreams, context, info); + addRanges(info, item.getID().toString()); + addSeeAlso(item); + } + + /** + * Returns a single sequence with canvases and item rendering (optional). + * @param item DSpace Item + * @param bitstreams list of bitstreams + * @param context the DSpace context + * @return a sequence of canvases + */ + private void addSequence(Item item, List bitstreams, Context context, Info info) { + sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); + if (bitstreams.size() > 0) { + addCanvas(sequenceGenerator, context, item, bitstreams, info); + } + addRendering(sequenceGenerator, item, context); + manifestGenerator.addSequence(sequenceGenerator); + } + + /** + * Adds DSpace Item metadata to the manifest. + * + * @param metadata list of DSpace metadata values + */ + private void addMetadata(List metadata) { + for (MetadataValue meta : metadata) { + String field = utils.getMetadataFieldName(meta); + if (field.contentEquals("rights.uri")) { + manifestGenerator.addMetadata(field, meta.getValue()); + manifestGenerator.addLicense(meta.getValue()); + } else if (field.contentEquals("description")) { + // Add manifest description field. + manifestGenerator.addDescription(field, meta.getValue()); + } else { + // Exclude DSpace description.provenance field. + if (!field.contentEquals("description.provenance")) { + // Everything else, add to manifest metadata fields. + manifestGenerator.addMetadata(field, meta.getValue()); + } + } + } + } + + /** + * A link to an external resource intended to be displayed directly to the user, + * and is related to the resource that has the related property. Examples might + * include a video or academic paper about the resource, a website, an HTML + * description, and so forth. + * + * This method adds a link to the Item represented in the DSpace Angular UI. + * + * @param item the DSpace Item + */ + private void addRelated(Item item) { + String url = CLIENT_URL + "/items/" + item.getID(); + otherContentGenerator.setIdentifier(url); + otherContentGenerator.setFormat("text/html"); + otherContentGenerator.setLabel(RELATED_ITEM_LABEL); + manifestGenerator.addRelated(otherContentGenerator); + } + + /** + * This method adds a canvas to the sequence for each item in the list of DSpace bitstreams. + * To be added bitstreams must be on image mime type. + * + * @param sequence the sequence object + * @param context the DSpace context + * @param item the DSpace Item + * @param bitstreams list of DSpace bitstreams + */ + private void addCanvas(CanvasItemsGenerator sequence, Context context, Item item, + List bitstreams, Info info) { + /** + * Counter tracks the position of the bitstream in the list and is used to create the canvas identifier. + * Bitstream order is determined by position in the IIIF DSpace bundle. + */ + int counter = 0; + for (Bitstream bitstream : bitstreams) { + UUID bitstreamID = bitstream.getID(); + String mimeType = utils.getBitstreamMimeType(bitstream, context); + if (utils.checkImageMimeType(mimeType)) { + CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, counter); + addImage(canvas, mimeType, bitstreamID); + if (counter == 2) { + addImage(canvas, mimeType, bitstreamID); + } + sequence.addCanvas(canvas); + counter++; + } + } + } + + /** + * A hint to the client as to the most appropriate method of displaying the resource. + * + * @param bitstreamCount count of bitstreams in the IIIF bundle. + */ + private void addViewingHint(int bitstreamCount) { + if (bitstreamCount > 2) { + manifestGenerator.addViewingHint(DOCUMENT_VIEWING_HINT); + } + } + + /** + * A link to a machine readable document that semantically describes the resource with + * the seeAlso property, such as an XML or RDF description. This document could be used + * for search and discovery or inferencing purposes, or just to provide a longer + * description of the resource. May have one or more external descriptions related to it. + * + * This method appends an AnnotationList of resources found in the Item's OtherContent bundle. + * A typical use case would be METS or ALTO files that describe the resource. + * + * @param item the DSpace Item. + */ + private void addSeeAlso(Item item) { + List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); + if (bundles.size() == 0) { + return; + } + otherContentGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso"); + otherContentGenerator.setType(AnnotationList.TYPE); + otherContentGenerator.setLabel(SEE_ALSO_LABEL); + manifestGenerator.addSeeAlso(otherContentGenerator); + } + + /** + * A link to an external resource intended for display or download by a human user. + * This property can be used to link from a manifest, collection or other resource + * to the preferred viewing environment for that resource, such as a viewer page on + * the publisher’s web site. Other uses include a rendering of a manifest as a PDF + * or EPUB. + * + * This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds + * it to the Sequence if found. + * + * @param sequence Sequence object + * @param item DSpace Item + * @param context DSpace context + */ + private void addRendering(CanvasItemsGenerator sequence, Item item, Context context) { + List bundles = item.getBundles("ORIGINAL"); + if (bundles.size() == 0) { + return; + } + Bundle bundle = bundles.get(0); + List bitstreams = bundle.getBitstreams(); + for (Bitstream bitstream : bitstreams) { + String mimeType = null; + try { + mimeType = bitstream.getFormat(context).getMIMEType(); + } catch (SQLException e) { + e.printStackTrace(); + } + // If the ORIGINAL bundle contains a PDF, assume that it represents the + // item and add to rendering. Ignore other mime-types. This convention should + // be documented. + if (mimeType != null && mimeType.contentEquals("application/pdf")) { + String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; + otherContentGenerator.setIdentifier(id); + otherContentGenerator.setLabel(PDF_DOWNLOAD_LABEL); + otherContentGenerator.setFormat(mimeType); + sequence.addRendering(otherContentGenerator); + } + } + } + + /** + * A link to a service that makes more functionality available for the resource, + * such as the base URI of an associated IIIF Search API service. + * + * This method returns a search service definition. Search scope is the manifest. + * + * @param item DSpace Item + * @return the IIIF search service definition for the item + */ + private void addSearchService(Item item) { + if (utils.isSearchable(item)) { + contentSearchGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/search"); + // TODO: get label from configuration then set on generator? + manifestGenerator.addService(contentSearchGenerator); + } + } + + /** + * Adds Ranges to manifest structures element. + * Ranges are defined in the info.json file. + * @param info + * @param identifier + */ + private void addRanges(Info info, String identifier) { + List rangesFromConfig = utils.getRangesFromInfoObject(info); + if (rangesFromConfig != null) { + for (int pos = 0; pos < rangesFromConfig.size(); pos++) { + setRange(identifier, rangesFromConfig.get(pos), pos); + manifestGenerator.addRange(rangeGenerator); + } + } + } + + /** + * Sets properties on the RangeFacade. + * @param identifier DSpace item id + * @param range range from info.json configuration + * @param pos list position of the range + */ + private void setRange(String identifier, RangeModel range, int pos) { + String id = IIIF_ENDPOINT + identifier + "/r" + pos; + String label = range.getLabel(); + rangeGenerator.setIdentifier(id); + rangeGenerator.setLabel(label); + String startCanvas = utils.getCanvasId(range.getStart()); + rangeGenerator.addCanvas(canvasService.getRangeCanvasReference(identifier, startCanvas)); + } + + /** + * Adds thumbnail to the manifest + * @param bitstreams + * @param context + */ + private void addThumbnail(List bitstreams, Context context) { + if (bitstreams.size() > 0) { + String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context); + if (utils.checkImageMimeType(mimeType)) { + manifestGenerator.addThumbnail(getThumbnailAnnotation(bitstreams.get(0).getID(), mimeType)); + } + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java new file mode 100644 index 0000000000..a16e0e32ab --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -0,0 +1,213 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; +import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; +import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Implements IIIF Search API queries and responses. + */ +@Component +@RequestScope +public class SearchService extends AbstractResourceService { + + @Autowired + IIIFUtils utils; + + @Autowired + ContentAsTextGenerator contentAsText; + + @Autowired + CanvasGenerator canvas; + + @Autowired + AnnotationGenerator annotation; + + @Autowired + ManifestGenerator manifest; + + @Autowired + SearchResultGenerator searchResult; + + public SearchService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Executes a search that is scoped to the manifest. + * + * @param uuid the IIIF manifest uuid + * @param query the solr query + * @return IIIF json + */ + public String searchWithinManifest(UUID uuid, String query) { + String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); + String json = getSolrSearchResponse(createSearchUrl(encodedQuery, getManifestId(uuid))); + return getAnnotationList(json, uuid, encodedQuery); + } + + /** + * Executes the Search API solr query. + * @param url solr query url + * @return json query response + */ + private String getSolrSearchResponse(URL url) { + InputStream jsonStream; + String json = null; + try { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Accept", "application/json"); + jsonStream = connection.getInputStream(); + json = IOUtils.toString(jsonStream, StandardCharsets.UTF_8); + } catch (IOException e) { + e.printStackTrace(); + } + return json; + } + + /** + * Constructs a solr search URL. + * + * @param encodedQuery the search terms + * @param manifestId the id of the manifest in which to search + * @return solr query + */ + private URL createSearchUrl(String encodedQuery, String manifestId) { + + String fullQuery = SEARCH_URL + "/select?" + + "q=ocr_text:\"" + encodedQuery + + "\"%20AND%20manifest_url:\"" + manifestId + "\"" + + "&hl=true" + + "&hl.ocr.fl=ocr_text" + + "&hl.ocr.contextBlock=line" + + "&hl.ocr.contextSize=2" + + "&hl.snippets=10" + + "&hl.ocr.limitBlock=page" + + "&hl.ocr.absoluteHighlights=true"; + try { + URL url = new URL(fullQuery); + return url; + } catch (MalformedURLException e) { + throw new RuntimeException("Malformed query URL", e); + } + } + + /** + * Generates a Search API response from the word_highlighting solr query response. + * + * The function assumes that the solr query responses contains page IDs + * (taken from the ALTO Page ID element) in the following format: + * Page.0, Page.1, Page.2.... + * + * The identifier values must be aligned with zero-based IIIF canvas identifiers: + * c0, c1, c2.... + * + * The convention convention for Alto IDs must be followed when indexing ALTO files + * into the word_highlighting solr index. If it is not, search responses will not + * match canvases. + * + * @param json solr search result + * @param uuid DSpace Item uuid + * @param encodedQuery the solr query + * @return a search response in JSON + */ + private String getAnnotationList(String json, UUID uuid, String encodedQuery) { + searchResult.setIdentifier(getManifestId(uuid) + "/search?q=" + encodedQuery); + GsonBuilder builder = new GsonBuilder(); + Gson gson = builder.create(); + JsonObject body = gson.fromJson(json, JsonObject.class); + // outer ocr highlight element + JsonObject highs = body.getAsJsonObject("ocrHighlighting"); + // highlight entries + for (Map.Entry ocrIds: highs.entrySet()) { + // ocr_text + JsonObject ocrObj = ocrIds.getValue().getAsJsonObject().getAsJsonObject("ocr_text"); + // snippets array + if (ocrObj != null) { + for (JsonElement snippetArray : ocrObj.getAsJsonObject().get("snippets").getAsJsonArray()) { + for (JsonElement highlights : snippetArray.getAsJsonObject().getAsJsonArray("highlights")) { + for (JsonElement highlight : highlights.getAsJsonArray()) { + JsonObject hcoords = highlight.getAsJsonObject(); + String text = (hcoords.get("text").getAsString()); + String pageId = getCanvasId((hcoords.get("page").getAsString())); + Integer ulx = hcoords.get("ulx").getAsInt(); + Integer uly = hcoords.get("uly").getAsInt(); + Integer lrx = hcoords.get("lrx").getAsInt(); + Integer lry = hcoords.get("lry").getAsInt(); + String w = Integer.toString(lrx - ulx); + String h = Integer.toString(lry - uly); + String params = ulx + "," + uly + "," + w + "," + h; + AnnotationGenerator annot = createSearchResultAnnotation(params, text, pageId, uuid); + searchResult.addResource(annot); + } + } + } + } + } + return utils.asJson(searchResult.getResource()); + } + + private String getCanvasId(String altoId) { + String[] identArr = altoId.split("\\."); + return "c" + identArr[1]; + } + + /** + * Creates annotation with word highlight coordinates. + * + * @param params word coordinate parameters used for highlighting. + * @param text word text + * @param pageId the page id returned by solr + * @param uuid the dspace item identifier + * @return a single annotation object that contains word highlights on a single page (canvas) + */ + private AnnotationGenerator createSearchResultAnnotation(String params, String text, String pageId, UUID uuid) { + annotation.setIdentifier(IIIF_ENDPOINT + uuid + "/annot/" + pageId + "-" + + params); + canvas.setIdentifier(IIIF_ENDPOINT + uuid + "/canvas/" + pageId + "#xywh=" + + params); + annotation.setOnCanvas(canvas); + contentAsText.setText(text); + annotation.setResource(contentAsText); + annotation.setMotivation(AnnotationGenerator.PAINTING); + List withinList = new ArrayList<>(); + manifest.setIdentifier(getManifestId(uuid)); + manifest.setLabel("Search within manifest."); + withinList.add(manifest); + annotation.setWithin(withinList); + return annotation; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java new file mode 100644 index 0000000000..e41815c445 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -0,0 +1,314 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service.util; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.apache.log4j.Logger; +import org.dspace.app.rest.iiif.model.ObjectMapperFactory; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.app.rest.iiif.model.info.RangeModel; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class IIIFUtils { + + private static final Logger log = Logger.getLogger(IIIFUtils.class); + + // The canvas position will be appended to this string. + private static final String CANVAS_PATH_BASE = "/canvas/c"; + + // get dbmdz module subclass. + protected SimpleModule iiifModule = ObjectMapperFactory.getIiifModule(); + // Use the dbmdz object mapper subclass. + protected ObjectMapper mapper = ObjectMapperFactory.getIiifObjectMapper(); + + @Autowired + protected BitstreamService bitstreamService; + + /** + * For IIIF entities, this method returns the bundle assigned to IIIF + * bitstreams. If the item is not an IIIF entity, the default (ORIGINAL) + * bundle list is returned instead. + * @param item the DSpace item + * @param iiifBundle the name of the IIIF bundle + * @return DSpace bundle + */ + public List getIiifBundle(Item item, String iiifBundle) { + boolean iiif = item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString().contentEquals("relationship_type")) + .anyMatch(m -> m.getValue().contentEquals("IIIF") || + m.getValue().contentEquals("IIIFSearchable")); + List bundles; + if (iiif) { + bundles = item.getBundles(iiifBundle); + } else { + bundles = item.getBundles(); + } + return bundles; + } + + /** + * Returns the requested bundle. + * @param item DSpace item + * @param name bundle name + * @return + */ + public List getBundle(Item item, String name) { + return item.getBundles(name); + } + + /** + * Returns bitstreams for the first bundle in the list. + * @param bundles list of DSpace bundles + * @return list of bitstreams + */ + public List getBitstreams(List bundles) { + if (bundles == null || bundles.size() == 0) { + throw new RuntimeException("Unable to retrieve DSpace bundle for manifest."); + } + return bundles.get(0).getBitstreams(); + } + + /** + * Returns the bitstream mime type + * @param bitstream DSpace bitstream + * @param context DSpace context + * @return mime type + */ + public String getBitstreamMimeType(Bitstream bitstream, Context context) { + try { + BitstreamFormat bitstreamFormat = bitstream.getFormat(context); + return bitstreamFormat.getMIMEType(); + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Checks to see if the item is searchable. Based on the entity type. + * @param item DSpace item + * @return true if searchable + */ + public boolean isSearchable(Item item) { + return item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString().contentEquals("relationship_type")) + .anyMatch(m -> m.getValue().contentEquals("IIIFSearchable")); + } + + /** + * Returns a metadata field name. + * @param meta the DSpace metadata value object + * @return field name as string + */ + public String getMetadataFieldName(MetadataValue meta) { + String element = meta.getMetadataField().getElement(); + String qualifier = meta.getMetadataField().getQualifier(); + // Need to distinguish DC type from DSpace relationship.type. + // Setting element to be the schema name. + if (meta.getMetadataField().getMetadataSchema().getName().contentEquals("relationship")) { + qualifier = element; + element = meta.getMetadataField().getMetadataSchema().getName(); + } + String field = element; + // Add qualifier if defined. + if (qualifier != null) { + field = field + "." + qualifier; + } + return field; + } + + /** + * Retrives a bitstream based on its position in the IIIF bundle. + * @param item DSpace Item + * @param canvasPosition bitstream position + * @return bitstream + */ + public Bitstream getBitstreamForCanvas(Item item, String bundleName, int canvasPosition) { + List bundles = item.getBundles(bundleName); + if (bundles.size() == 0) { + return null; + } + List bitstreams = bundles.get(0).getBitstreams(); + try { + return bitstreams.get(canvasPosition); + } catch (RuntimeException e) { + throw new RuntimeException("The requested canvas is not available", e); + } + } + + /** + * Attempts to find info.json file in the bitstream bundle and convert + * the json into the Info.class domain model for canvas and range parameters. + * @param context DSpace context + * @param bundleName the IIIF bundle + * @return info domain model + */ + public Info getInfo(Context context, Item item, String bundleName) { + Info info = null; + try { + ObjectMapper mapper = new ObjectMapper(); + // Look for expected json file bitstream in bundle. + Bitstream infoBitstream = bitstreamService + .getBitstreamByName(item, bundleName, "info.json"); + if (infoBitstream != null) { + InputStream is = bitstreamService.retrieve(context, infoBitstream); + info = mapper.readValue(is, Info.class); + } + } catch (IOException | SQLException e) { + log.warn("Unable to read info.json file.", e); + } catch (AuthorizeException e) { + log.warn("Not authorized to access info.json file.", e); + } + return info; + } + + /** + * Returns the range parameter List or null + * @param info the parameters model + * @return list of range models + */ + public List getRangesFromInfoObject(Info info) { + if (info != null) { + return info.getStructures(); + } + return null; + } + + /** + * Extracts canvas position from the URL input path. + * @param canvasId e.g. "c12" + * @return the position, e.g. 12 + */ + public int getCanvasId(String canvasId) { + return Integer.parseInt(canvasId.substring(1)); + } + + /** + * Returns the canvas path with position. The path + * returned is partial, not the fully qualified URI. + * @param position position of the bitstream in the DSpace bundle. + * @return partial canvas path. + */ + public String getCanvasId(int position) { + return CANVAS_PATH_BASE + position; + } + + /** + * Convenience method to compare canvas parameter and bitstream list size. + * @param info the parameter model + * @param bitstreams the list of DSpace bitstreams + * @return true if sizes match + */ + public boolean isListSizeMatch(Info info, List bitstreams) { + // If Info is not null then the bitstream bundle contains info.json; exclude + // the file from comparison. + if (info != null && info.getCanvases().size() == bitstreams.size() - 1) { + return true; + } + return false; + } + + /** + * Convenience method verifies that the requested canvas exists in the + * parameters model object. + * @param info parameter model + * @param canvasPosition requested canvas position + * @return true if index is in bounds + */ + public boolean canvasOutOfBounds(Info info, int canvasPosition) { + return canvasPosition < 0 || canvasPosition >= info.getCanvases().size(); + } + + /** + * Validates info.json for a single canvas. + * Unless global settings are being used, when canvas information is not available + * use defaults. The canvas information is defined in the info.json file. + * @param info the information model + * @param position the position of the requested canvas + * @return information model + */ + public Info validateInfoForSingleCanvas(Info info, int position) { + if (info != null && info.getGlobalDefaults() != null) { + if (canvasOutOfBounds(info, position) && !info.getGlobalDefaults().isActivated()) { + log.warn("Canvas for position " + position + " not defined.\n" + + "Ignoring info.json canvas definitions and using defaults. " + + "Any other canvas-level annotations will also be ignored."); + info.setCanvases(new ArrayList<>()); + } + } + return info; + } + + /** + * Unless global settings are being used, when canvas information list size does + * not match the number of bitstreams use defaults. The canvas information is + * defined in the info.json file. + * @param info the information model + * @param bitstreams the list of bitstreams + * @return information model + */ + public Info validateInfoForManifest(Info info, List bitstreams) { + if (info != null && info.getGlobalDefaults() != null) { + if (!isListSizeMatch(info, bitstreams) && !info.getGlobalDefaults().isActivated()) { + log.warn("Mismatch between info.json canvases and DSpace bitstream count.\n" + + "Ignoring info.json canvas definitions and using defaults." + + "Any other canvas-level annotations will also be ignored."); + info.setCanvases(new ArrayList<>()); + } + } + return info; + } + + /** + * Serializes the json response. + * @param resource to be serialized + * @return + */ + public String asJson(Resource resource) { + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.registerModule(iiifModule); + try { + return mapper.writeValueAsString(resource); + } catch (JsonProcessingException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * Tests for image mimetype. Presentation API 2.1.1 canvas supports images only. + * Other media types introduced in version 3. + * @param mimetype + * @return true if an image + */ + public boolean checkImageMimeType(String mimetype) { + if (mimetype != null && mimetype.contains("image/")) { + return true; + } + return false; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java new file mode 100644 index 0000000000..63fa43910e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.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.iiif.service.util; + +import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ImageProfileUtil { + + @Autowired + ProfileGenerator profile; + + /** + * Utility method for obtaining the image service profile. + * Calling from this utility provides a unique instance of the + * autowired property. Necessary because a single canvas resource contains + * both thumbnail and images. + * + * @return image service profile + */ + public ProfileGenerator getImageProfile() throws + RuntimeException { + profile.setIdentifier("http://iiif.io/api/image/2/level1.json"); + return profile; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java new file mode 100644 index 0000000000..58e5dd18b3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java @@ -0,0 +1,34 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service.util; + +import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ThumbProfileUtil { + + @Autowired + ProfileGenerator profile; + + /** + * Utility method for obtaining the thumbnail image service profile. + * Calling from this utility provides a unique instance of the + * autowired property. Necessary because a single canvas resource contains + * both thumbnail and images. + * + * @return the thumbnail service profile + */ + public ProfileGenerator getThumbnailProfile() throws + RuntimeException { + profile.setIdentifier("http://iiif.io/api/image/2/level0.json"); + return profile; + } + +} From 988b221e0da3abdfee38fa552e59ffde0490d697 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:53:28 -0700 Subject: [PATCH 0037/1254] Added iiif repository. --- .../app/rest/iiif/IIIFRestRepository.java | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java new file mode 100644 index 0000000000..1ea10fe24e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java @@ -0,0 +1,129 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif; + +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.app.rest.iiif.service.AnnotationListService; +import org.dspace.app.rest.iiif.service.CanvasLookupService; +import org.dspace.app.rest.iiif.service.ManifestService; +import org.dspace.app.rest.iiif.service.SearchService; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Repository for IIIF Presentation and Search API requests. + */ +@Component +public class IIIFRestRepository { + + @Autowired + ItemService itemService; + + @Autowired + BitstreamService bitstreamService; + + @Autowired + ManifestService manifestService; + + @Autowired + SearchService searchService; + + @Autowired + AnnotationListService annotationListService; + + @Autowired + CanvasLookupService canvasLookupService; + + /** + * The manifest response contains sufficient information for the client to initialize itself + * and begin to display something quickly to the user. The manifest resource represents a single + * object and any intellectual work or works embodied within that object. In particular it + * includes the descriptive, rights and linking information for the object. It then embeds + * the sequence(s) of canvases that should be rendered to the user. + * + * Returns manifest for single DSpace item. + * + * @param context DSpace context + * @param id DSpace Item uuid + * @return manifest as JSON + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public String getManifest(Context context, UUID id) + throws ResourceNotFoundException { + Item item; + try { + item = itemService.find(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (item == null) { + throw new ResourceNotFoundException("IIIF manifest for id " + id + " not found"); + } + return manifestService.getManifest(item, context); + } + + /** + * The canvas represents an individual page or view and acts as a central point for + * laying out the different content resources that make up the display. This information + * should be embedded within a sequence. + * + * @param context DSpace context + * @param id DSpace item uuid + * @param canvasId canvas identifier + * @return canvas as JSON + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public String getCanvas(Context context, UUID id, String canvasId) + throws ResourceNotFoundException { + Item item; + try { + item = itemService.find(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (item == null) { + throw new ResourceNotFoundException("IIIF canvas for id " + id + " not found"); + } + return canvasLookupService.generateCanvas(context, item, canvasId); + } + + /** + * Returns search hits and word coordinates as an AnnotationList. + * + * Search scope is a single DSpace item or manifest. + * + * @param id DSpace item uuid + * @param query query terms + * @return AnnotationList as JSON + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public String searchInManifest(UUID id, String query) { + + return searchService.searchWithinManifest(id, query); + } + + /** + * Returns annotations for machine readable metadata that describes the resource. + * + * @param context DSpace context + * @param id the Item uuid + * @return AnnotationList as JSON + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public String getSeeAlsoAnnotations(Context context, UUID id) { + return annotationListService.getSeeAlsoAnnotations(context, id); + } +} From 1b6c770d236c74a67e59548fb2ff893fbeb1f8cf Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:54:34 -0700 Subject: [PATCH 0038/1254] Added iiif controller. --- .../org/dspace/app/rest/IIIFController.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java new file mode 100644 index 0000000000..64046f883a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java @@ -0,0 +1,107 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import java.util.UUID; + +import org.dspace.app.rest.iiif.IIIFRestRepository; +import org.dspace.app.rest.repository.AbstractDSpaceRestRepository; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for IIIF Presentation and Search API. + */ +@RestController +@RequestMapping("/api/iiif") +public class IIIFController extends AbstractDSpaceRestRepository { + + @Autowired + IIIFRestRepository iiifRestRepository; + + /** + * The manifest response contains sufficient information for the client to initialize + * itself and begin to display something quickly to the user. The manifest resource + * represents a single object and any intellectual work or works embodied within that + * object. In particular it includes the descriptive, rights and linking information + * for the object. It then embeds the sequence(s) of canvases that should be rendered + * to the user. + * + * Called with GET to retrieve the manifest for a single DSpace item. + * + * @param id DSpace Item uuid + * @return manifest as JSON + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest") + public String findOne(@PathVariable UUID id) { + Context context = obtainContext(); + return iiifRestRepository.getManifest(context, id); + } + + /** + * Any resource in the Presentation API may have a search service associated with it. + * The resource determines the scope of the content that will be searched. A service + * associated with a manifest will search all of the annotations on canvases or other + * objects below the manifest, a service associated with a particular range will only + * search the canvases within the range, or a service on a canvas will search only + * annotations on that particular canvas. The URIs for services associated with different + * resources must be different to allow the client to use the correct one for the desired + * scope of the search. + * + * This endpoint for searches within the manifest scope (by DSpace item uuid). + * + * @param id DSpace Item uuid + * @param q query terms + * @return AnnotationList as JSON + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest/search") + public String searchInManifest(@PathVariable UUID id, + @RequestParam(name = "q") String q) { + return iiifRestRepository.searchInManifest(id, q); + } + + /** + * All resources can link to semantic descriptions of themselves via the seeAlso property. + * These could be METS, ALTO, full text, or a schema.org descriptions. + * + * Since there's currently no reliable way to associate "seeAlso" links and individual + * canvases (e.g. associate a single image with its ALTO file) the + * scope is the entire manifest (or DSpace Item). + * + * @param id DSpace Item uuid + * @return AnnotationList as JSON + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest/seeAlso") + public String findSeeAlsoList(@PathVariable UUID id) { + Context context = obtainContext(); + return iiifRestRepository.getSeeAlsoAnnotations(context, id); + } + + /** + * The canvas represents an individual page or view and acts as a central point for + * laying out the different content resources that make up the display. This information + * should be embedded within a sequence. + * + * This endpoint allows canvases to be dereferenced separately from the manifest. This + * is an atypical use case. + * + * @param id DSpace Item uuid + * @param cid canvas identifier + * @return canvas as JSON + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}/canvas/{cid}") + public String findCanvas(@PathVariable UUID id, @PathVariable String cid) { + Context context = obtainContext(); + return iiifRestRepository.getCanvas(context, id, cid); + } +} From fb72c2a4b51effaa5fb1d3094fc5448f8c4d556a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:56:55 -0700 Subject: [PATCH 0039/1254] Added integration tests. --- .../dspace/app/iiif/IIIFRestRepositoryIT.java | 569 ++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java new file mode 100644 index 0000000000..8a88b21f49 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java @@ -0,0 +1,569 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif; + +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.InputStream; + +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { + + public static final String IIIFBundle = "IIIF"; + + /** + * info.json set for individual canvases. + */ + String info = "{\"globalDefaults\":{\"activated\": false,\"label\": \"\",\"width\": 0,\"height\": 0}," + + "\"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, \"height\": 4220, \"pos\": 0}]" + + ",\"structures\": []}"; + + /** + * info.json with structures and global canvas setting. + */ + String infoWithStructures = "{\"globalDefaults\":" + + "{\"activated\": true,\"label\": \"Global\",\"width\": 2000,\"height\": 3000}," + + "\"canvases\":[]," + + "\"structures\": " + + "[{\"label\":\"Section 1\",\"start\":1}," + + "{\"label\":\"Section 2\",\"start\":2}]" + + "}"; + + /** + * info.json defaulting to global canvas settings. + */ + String globalInfoConfig = "{\"globalDefaults\":{\"activated\": true,\"label\": \"Global\",\"width\": 2000," + + "\"height\": 3000}, \"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, " + + "\"height\": 4220, \"pos\": 0}],\"structures\": []}"; + + @Test + public void missingBundleTest() 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") + .withRelationshipType("IIIFSearchable") + .build(); + context.restoreAuthSystemState(); + // Status 500 + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().is(500)); + + } + + @Test + public void findOneIIIFSearchableEntityTypeIT() 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") + .withRelationshipType("IIIFSearchable") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + context.restoreAuthSystemState(); + + // Default canvas size and label. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.service.profile", is("http://iiif.io/api/search/0/search"))) + .andExpect(jsonPath("$.thumbnail.@id", Matchers.containsString("/iiif/2/" + + bitstream1.getID()))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(1200))) + .andExpect(jsonPath("$.related.@id", + Matchers.containsString("/items/" + publicItem1.getID()))); + } + + @Test + public void findOneIIIFSearchableWithGlobalConfigIT() 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") + .withRelationshipType("IIIFSearchable") + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(this.globalInfoConfig, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, "IIIF") + .withName("info.json") + .withMimeType("application/json") + .build(); + } + + context.restoreAuthSystemState(); + // Expect canvas label, width and height to match bitstream description. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) + .andExpect(jsonPath("$.service").exists()); + } + + @Test + public void findOneIIIFSearchableWithInfoJsonIT() 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") + .withRelationshipType("IIIFSearchable") + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(this.info, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, "IIIF") + .withName("info.json") + .withMimeType("application/json") + .build(); + } + + context.restoreAuthSystemState(); + // Expect canvas label, width and height to match bitstream description. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(3163))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(4220))) + .andExpect(jsonPath("$.service").exists()); + } + + @Test + public void findOneIIIFEntityPagedHintIT() 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") + .withMetadata("dc", "rights", "uri", "https://license.org") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream2") + .withMimeType("image/jpeg") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream3") + .withMimeType("image/jpeg") + .build(); + } + + context.restoreAuthSystemState(); + + // With more than 2 bitstreams in IIIF bundle, the sequence viewing hint should be "paged" + // unless that has been changed in dspace configuration. This test assumes that DSpace + // has been configured to return the "individuals" hint for documents to better support + // search results in Mirador. That is the current dspace.cfg default setting. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.license", is("https://license.org"))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.viewingHint", is("individuals"))) + .andExpect(jsonPath("$.service").doesNotExist()); + + } + + @Test + public void findOneWithStructures() 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") + .withRelationshipType("IIIFSearchable") + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream2") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream3") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(this.infoWithStructures, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, "IIIF") + .withName("info.json") + .withMimeType("application/json") + .build(); + } + + context.restoreAuthSystemState(); + // expect structures elements with label and canvas id. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) + .andExpect(jsonPath("$.structures[1].label", is("Section 2"))) + .andExpect(jsonPath("$.structures[1].canvases[0]", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c2"))) + .andExpect(jsonPath("$.service").exists()); + } + + @Test + public void findOneIIIFEntityTypeIT() 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") + .withMetadata("dc", "rights", "uri", "https://license.org") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.license", is("https://license.org"))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.service").doesNotExist()); + + } + + @Test + public void findOneIIIFWithOtherContentIT() 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") + .withMetadata("dc", "rights", "uri", "https://license.org") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, "OtherContent") + .withName("file.xml") + .withMimeType("application/xml") + .build(); + } + + context.restoreAuthSystemState(); + + // Expect seeAlso annotation list. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.license", is("https://license.org"))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.seeAlso.@type", is("sc:AnnotationList"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.service").doesNotExist()); + + } + + @Test + public void findOneUsingOriginalBundleIgnoreFileIT() 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") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withMimeType("application/pdf") + .build(); + } + + context.restoreAuthSystemState(); + + // Image in the ORIGINAL bundle added as canvas; PDF ignored... + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases", Matchers.hasSize(1))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.service").doesNotExist()); + } + + + @Test + public void findOneCanvas() 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") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("IMG1") + .withMimeType("image/jpeg") + .build(); + } + context.restoreAuthSystemState(); + + // Single canvas. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/canvas/c0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@type", is("sc:Canvas"))) + .andExpect(jsonPath("$.images[0].@type", is("oa:Annotation"))); + } + + @Test + public void missingCanvas() 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") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("IMG1") + .withMimeType("image/jpeg") + .build(); + } + context.restoreAuthSystemState(); + // Status 500. The item contains only one bitstream. The item manifest likewise contains one canvas. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/canvas/c2")) + .andExpect(status().is(500)); + } + + @Test + public void getAnnotationListSeeAlso() 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") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("IMG1") + .withMimeType("image/jpeg") + .build(); + } + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is, "OtherContent") + .withName("file.xml") + .withMimeType("application/xml") + .build(); + } + + context.restoreAuthSystemState(); + + // Expect seeAlso AnnotationList if the dspace item includes an OtherContent bundle. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest/seeAlso")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@type", is("sc:AnnotationList"))) + .andExpect(jsonPath("$.resources[0].@type", is("oa:Annotation"))) + .andExpect(jsonPath("$.resources[0].resource.@id", + Matchers.containsString(bitstream2.getID() + "/content"))); + + } +} From a3a809ea6b8359f9aabf07ad5282dd18ad2c94c1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 16:04:45 -0700 Subject: [PATCH 0040/1254] Added new method to the test BitstreamBuilder and moved iiif test package. --- .../org/dspace/builder/BitstreamBuilder.java | 33 ++++++++++++++++++- .../{ => rest}/iiif/IIIFRestRepositoryIT.java | 0 2 files changed, 32 insertions(+), 1 deletion(-) rename dspace-server-webapp/src/test/java/org/dspace/app/{ => rest}/iiif/IIIFRestRepositoryIT.java (100%) diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index b8942a17d0..da9eddacd5 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -49,6 +49,12 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return builder.create(context, bundle, is); } + public static BitstreamBuilder createBitstream(Context context, Item item, InputStream is, String bundleName) + throws SQLException, AuthorizeException, IOException { + BitstreamBuilder builder = new BitstreamBuilder(context); + return builder.createInRequestedBundle(context, item, is, bundleName); + } + private BitstreamBuilder create(Context context, Item item, InputStream is) throws SQLException, AuthorizeException, IOException { this.context = context; @@ -70,6 +76,31 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return this; } + private BitstreamBuilder createInRequestedBundle(Context context, Item item, InputStream is, String bundleName) + throws SQLException, AuthorizeException, IOException { + this.context = context; + this.item = item; + + Bundle bundle = getBundleByName(item, bundleName); + + bitstream = bitstreamService.create(context, bundle, is); + + return this; + } + + private Bundle getBundleByName(Item item, String bundleName) throws SQLException, AuthorizeException { + List bundles = itemService.getBundles(item, bundleName); + Bundle targetBundle = null; + + if (bundles.size() < 1) { + // not found, create a new one + targetBundle = bundleService.create(context, item, bundleName); + } else { + // put bitstreams into first bundle + targetBundle = bundles.iterator().next(); + } + return targetBundle; + } public BitstreamBuilder withName(String name) throws SQLException { bitstream.setName(context, name); @@ -168,4 +199,4 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { protected DSpaceObjectService getService() { return bitstreamService; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java similarity index 100% rename from dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java From 78c66f3fedc659fd951f7d5ce47886ce03520796 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 26 Mar 2021 16:36:54 -0700 Subject: [PATCH 0041/1254] Added sample iiif config to dspace.cfg --- dspace/config/dspace.cfg | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index f4ba5901f7..bc5bc5a7fe 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1497,6 +1497,23 @@ log.report.dir = ${dspace.dir}/log # Take this key (just the UA-XXXXXX-X part) and place it here in this parameter. # google.analytics.key=UA-XXXXXX-X +#### IIIF CONFIGURATION #### +# Base URL of the DSpace iiif API endpoint. +#iiif.url = ${dspace.server.url}/api/iiif/ + +# Base URL of the IIIF image server. +#iiif.image.server = http://localhost:8182/iiif/2/ + +# Base URL of the solr search index (IIIF Search API). +#iiif.solr.search.url = http://localhost:8983/solr/word_highlighting + +#iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams + +# Possible values: "paged" or "individuals". Typically "paged" is preferred +# for documents. However, it can be overridden here if necessary for the +# viewer client. +#iiif.document.viewing.hint = individuals + #---------------------------------------------------------------# #----------------REQUEST ITEM CONFIGURATION---------------------# #---------------------------------------------------------------# From c39bd6c7c2af1c1d3d09f954551195065b167460 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 26 Mar 2021 16:37:45 -0700 Subject: [PATCH 0042/1254] Minor refactor manifest generator --- .../iiif/model/generator/ManifestGenerator.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 9022a7c960..7451114284 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -56,13 +56,13 @@ public class ManifestGenerator implements IIIFResource { private List ranges = new ArrayList<>(); @Autowired - org.dspace.app.rest.iiif.model.generator.PropertyValueGenerator propertyValue; + PropertyValueGenerator propertyValue; @Autowired - org.dspace.app.rest.iiif.model.generator.MetadataEntryGenerator metadataEntry; + MetadataEntryGenerator metadataEntryGenerator; @Autowired - BehaviorGenerator behaviorFascade; + BehaviorGenerator behaviorGenerator; /** * Sets the mandatory Manifest ID. @@ -86,8 +86,8 @@ public class ManifestGenerator implements IIIFResource { * @param viewingHint */ public void addViewingHint(String viewingHint) { - behaviorFascade.setType(viewingHint); - this.viewingHint = behaviorFascade.getValue(); + behaviorGenerator.setType(viewingHint); + this.viewingHint = behaviorGenerator.getValue(); } /** @@ -137,9 +137,9 @@ public class ManifestGenerator implements IIIFResource { * @param value */ public void addMetadata(String field, String value) { - metadataEntry.setField(field); - metadataEntry.setValue(value); - metadata.add(metadataEntry.getValue()); + metadataEntryGenerator.setField(field); + metadataEntryGenerator.setValue(value); + metadata.add(metadataEntryGenerator.getValue()); } /** From 256f89bbbeeb4809d23158ab6f3abe6bc3658ea2 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 26 Mar 2021 17:45:40 -0700 Subject: [PATCH 0043/1254] Updated the image service thumbnail path. --- .../dspace/app/rest/iiif/service/AbstractResourceService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 6b87cae231..c1e33508c3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -46,7 +46,7 @@ public abstract class AbstractResourceService { protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; // Paths for IIIF Image API requests. - protected static final String THUMBNAIL_PATH = "/full/,90/0/default.jpg"; + protected static final String THUMBNAIL_PATH = "/full/90,/0/default.jpg"; protected static final String IMAGE_PATH = "/full/full/0/default.jpg"; @Autowired From b31d3ec98fc4fdc8f97bb8944ef5107eae06e2c8 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 28 Mar 2021 10:25:24 -0700 Subject: [PATCH 0044/1254] Updated dspace.cfg comment. --- dspace/config/dspace.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index bc5bc5a7fe..2588a3a3d5 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1509,9 +1509,9 @@ log.report.dir = ${dspace.dir}/log #iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams -# Possible values: "paged" or "individuals". Typically "paged" is preferred -# for documents. However, it can be overridden here if necessary for the -# viewer client. +# Sets the viewing hint. Possible values: "paged" or "individuals". +# Typically "paged" is preferred for multi-age documents. Use "individuals" +# if you plan to implement the search api. #iiif.document.viewing.hint = individuals #---------------------------------------------------------------# From 47e835db0d2f43e769073666b7ceb526fe9467dc Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 28 Mar 2021 17:06:49 -0700 Subject: [PATCH 0045/1254] Added exception to search service query. --- .../dspace/app/rest/iiif/service/SearchService.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index a16e0e32ab..fb12fbe277 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -24,6 +24,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; @@ -42,6 +43,8 @@ import org.springframework.web.context.annotation.RequestScope; @RequestScope public class SearchService extends AbstractResourceService { + private static final Logger log = Logger.getLogger(SearchService.class); + @Autowired IIIFUtils utils; @@ -84,7 +87,7 @@ public class SearchService extends AbstractResourceService { */ private String getSolrSearchResponse(URL url) { InputStream jsonStream; - String json = null; + String json; try { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); @@ -92,7 +95,7 @@ public class SearchService extends AbstractResourceService { jsonStream = connection.getInputStream(); json = IOUtils.toString(jsonStream, StandardCharsets.UTF_8); } catch (IOException e) { - e.printStackTrace(); + throw new RuntimeException("Unable to query solr at: " + url, e); } return json; } @@ -114,8 +117,11 @@ public class SearchService extends AbstractResourceService { "&hl.ocr.contextBlock=line" + "&hl.ocr.contextSize=2" + "&hl.snippets=10" + - "&hl.ocr.limitBlock=page" + + // "&hl.ocr.limitBlock=page" + "&hl.ocr.absoluteHighlights=true"; + + log.debug(fullQuery); + try { URL url = new URL(fullQuery); return url; From 8e1f0ca85bea3a3a9cc474df573454b3c8674cc2 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 29 Mar 2021 16:21:21 -0700 Subject: [PATCH 0046/1254] Updated search service to use solr client libraries. --- .../iiif/service/AbstractResourceService.java | 2 + .../app/rest/iiif/service/SearchService.java | 143 +++++++++++------- 2 files changed, 92 insertions(+), 53 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index c1e33508c3..a94803bb9f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.iiif.service; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.UUID; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index fb12fbe277..e1d6c6e5e2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -8,10 +8,6 @@ package org.dspace.app.rest.iiif.service; import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -21,10 +17,18 @@ import java.util.UUID; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import org.apache.commons.io.IOUtils; +import org.apache.commons.validator.routines.UrlValidator; import org.apache.log4j.Logger; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.impl.NoOpResponseParser; +import org.apache.solr.client.solrj.request.QueryRequest; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.util.NamedList; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; @@ -32,6 +36,7 @@ import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @@ -45,6 +50,8 @@ public class SearchService extends AbstractResourceService { private static final Logger log = Logger.getLogger(SearchService.class); + private final boolean validationEnabled; + @Autowired IIIFUtils utils; @@ -65,38 +72,53 @@ public class SearchService extends AbstractResourceService { public SearchService(ConfigurationService configurationService) { setConfiguration(configurationService); + validationEnabled = configurationService + .getBooleanProperty("discovery.solr.url.validation.enabled", true); } /** * Executes a search that is scoped to the manifest. * - * @param uuid the IIIF manifest uuid + * @param uuid dspace item uuid * @param query the solr query * @return IIIF json */ public String searchWithinManifest(UUID uuid, String query) { String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); - String json = getSolrSearchResponse(createSearchUrl(encodedQuery, getManifestId(uuid))); + String json = getSolrSearchResponse(encodedQuery, getManifestId(uuid)); return getAnnotationList(json, uuid, encodedQuery); } /** * Executes the Search API solr query. - * @param url solr query url + * @param encodedQuery encoded query terms + * @param manifestId the iiif manifest id + * * @return json query response */ - private String getSolrSearchResponse(URL url) { - InputStream jsonStream; - String json; - try { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setRequestProperty("Accept", "application/json"); - jsonStream = connection.getInputStream(); - json = IOUtils.toString(jsonStream, StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException("Unable to query solr at: " + url, e); + private String getSolrSearchResponse(String encodedQuery, String manifestId) { + String json = ""; + String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("iiif.solr.search.url"); + UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); + if (urlValidator.isValid(solrService) || this.validationEnabled) { + HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); + solrServer.setBaseURL(solrService); + solrServer.setUseMultiPartPost(true); + SolrQuery solrQuery = getSolrQuery(encodedQuery, manifestId); + QueryRequest req = new QueryRequest(solrQuery); + req.setResponseParser(new NoOpResponseParser("json")); + NamedList resp = null; + try { + resp = solrServer.request(req); + json = (String) resp.get("response"); + } catch (SolrServerException | IOException e) { + throw new RuntimeException("Unable to retrieve search response.", e); + } + } else { + log.error("Error while initializing solr, invalid url: " + solrService); } + return json; } @@ -107,27 +129,20 @@ public class SearchService extends AbstractResourceService { * @param manifestId the id of the manifest in which to search * @return solr query */ - private URL createSearchUrl(String encodedQuery, String manifestId) { + private SolrQuery getSolrQuery(String encodedQuery, String manifestId) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setQuery("ocr_text:" + encodedQuery + + " AND manifest_url:\"" + manifestId + "\""); + solrQuery.set(CommonParams.WT, "json"); + solrQuery.set("hl", "true"); + solrQuery.set("hl.ocr.fl", "ocr_text"); + solrQuery.set("hl.ocr.contextBlock", "line"); + solrQuery.set("hl.ocr.contextSize", "2"); + solrQuery.set("hl.snippets", "10"); + solrQuery.set("hl.ocr.limitBlock","page"); + solrQuery.set("hl.ocr.absoluteHighlights", "true"); - String fullQuery = SEARCH_URL + "/select?" + - "q=ocr_text:\"" + encodedQuery + - "\"%20AND%20manifest_url:\"" + manifestId + "\"" + - "&hl=true" + - "&hl.ocr.fl=ocr_text" + - "&hl.ocr.contextBlock=line" + - "&hl.ocr.contextSize=2" + - "&hl.snippets=10" + - // "&hl.ocr.limitBlock=page" + - "&hl.ocr.absoluteHighlights=true"; - - log.debug(fullQuery); - - try { - URL url = new URL(fullQuery); - return url; - } catch (MalformedURLException e) { - throw new RuntimeException("Malformed query URL", e); - } + return solrQuery; } /** @@ -154,6 +169,10 @@ public class SearchService extends AbstractResourceService { GsonBuilder builder = new GsonBuilder(); Gson gson = builder.create(); JsonObject body = gson.fromJson(json, JsonObject.class); + if (body == null) { + log.warn("Unable to process json response."); + return utils.asJson(searchResult.getResource()); + } // outer ocr highlight element JsonObject highs = body.getAsJsonObject("ocrHighlighting"); // highlight entries @@ -163,20 +182,10 @@ public class SearchService extends AbstractResourceService { // snippets array if (ocrObj != null) { for (JsonElement snippetArray : ocrObj.getAsJsonObject().get("snippets").getAsJsonArray()) { + String pageId = getCanvasId(snippetArray.getAsJsonObject().get("pages")); for (JsonElement highlights : snippetArray.getAsJsonObject().getAsJsonArray("highlights")) { for (JsonElement highlight : highlights.getAsJsonArray()) { - JsonObject hcoords = highlight.getAsJsonObject(); - String text = (hcoords.get("text").getAsString()); - String pageId = getCanvasId((hcoords.get("page").getAsString())); - Integer ulx = hcoords.get("ulx").getAsInt(); - Integer uly = hcoords.get("uly").getAsInt(); - Integer lrx = hcoords.get("lrx").getAsInt(); - Integer lry = hcoords.get("lry").getAsInt(); - String w = Integer.toString(lrx - ulx); - String h = Integer.toString(lry - uly); - String params = ulx + "," + uly + "," + w + "," + h; - AnnotationGenerator annot = createSearchResultAnnotation(params, text, pageId, uuid); - searchResult.addResource(annot); + searchResult.addResource(getAnnotation(highlight, pageId, uuid)); } } } @@ -185,8 +194,36 @@ public class SearchService extends AbstractResourceService { return utils.asJson(searchResult.getResource()); } - private String getCanvasId(String altoId) { - String[] identArr = altoId.split("\\."); + /** + * Returns the annotation generator for the highlight. + * @param highlight highlight element from solor response + * @param pageId page id from solr response + * @param uuid dspace item uuid + * @return generator for a single annotation + */ + private AnnotationGenerator getAnnotation(JsonElement highlight, String pageId, UUID uuid) { + JsonObject hcoords = highlight.getAsJsonObject(); + String text = (hcoords.get("text").getAsString()); + Integer ulx = hcoords.get("ulx").getAsInt(); + Integer uly = hcoords.get("uly").getAsInt(); + Integer lrx = hcoords.get("lrx").getAsInt(); + Integer lry = hcoords.get("lry").getAsInt(); + String w = Integer.toString(lrx - ulx); + String h = Integer.toString(lry - uly); + String params = ulx + "," + uly + "," + w + "," + h; + return createSearchResultAnnotation(params, text, pageId, uuid); + } + + /** + * Returns position of canvas by extracting from the pages id element. + * @param element the pages element + * @return canvas id + */ + private String getCanvasId(JsonElement element) { + JsonArray pages = element.getAsJsonArray(); + JsonObject page = pages.get(0).getAsJsonObject(); + String[] identArr = page.get("id").getAsString().split("\\."); + // the canvas id. return "c" + identArr[1]; } From 0ea5f13498bd59173c8ebe5d9cf695f55c5d034f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:20:06 -0700 Subject: [PATCH 0047/1254] Adding iiif-apis dependency --- LICENSES_THIRD_PARTY | 1 + dspace-server-webapp/pom.xml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 69aa0f74bd..5307f8ab0c 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -395,6 +395,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.14 - http://www.slf4j.org) * SLF4J API Module (org.slf4j:slf4j-api:1.7.14 - http://www.slf4j.org) * SLF4J LOG4J-12 Binding (org.slf4j:slf4j-log4j12:1.7.14 - http://www.slf4j.org) + * iiif-apis (de.digitalcollections.iiif:iiif-apis:0.3.9 - https://github.com/dbmdz/iiif-apis) Mozilla Public License: diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 5e17518a5e..f4ea1b299c 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -474,6 +474,37 @@ + + de.digitalcollections.iiif + iiif-apis + 0.3.7 + + + org.reflections + reflections + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.springframework.security + spring-security-core + + + org.dmfs + iterators + + + com.fasterxml.jackson.module + jackson-module-parameter-names + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.apache.solr solr-cell From 8dea0807b71d7487e3a791ac9eb82da289438a90 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:44:02 -0700 Subject: [PATCH 0048/1254] Added iiif model generator classes. --- .../model/generator/AnnotationGenerator.java | 91 ++++++++ .../generator/AnnotationListGenerator.java | 43 ++++ .../model/generator/BehaviorGenerator.java | 35 +++ .../iiif/model/generator/CanvasGenerator.java | 118 ++++++++++ .../model/generator/CanvasItemsGenerator.java | 78 +++++++ .../generator/ContentAsTextGenerator.java | 29 +++ .../generator/ContentSearchGenerator.java | 73 ++++++ .../generator/ExternalLinksGenerator.java | 93 ++++++++ .../iiif/model/generator/IIIFResource.java | 20 ++ .../iiif/model/generator/IIIFService.java | 19 ++ .../rest/iiif/model/generator/IIIFValue.java | 17 ++ .../generator/ImageContentGenerator.java | 74 ++++++ .../generator/ImageServiceGenerator.java | 56 +++++ .../model/generator/ManifestGenerator.java | 217 ++++++++++++++++++ .../generator/MetadataEntryGenerator.java | 31 +++ .../model/generator/ProfileGenerator.java | 38 +++ .../generator/PropertyValueGenerator.java | 35 +++ .../iiif/model/generator/RangeGenerator.java | 73 ++++++ .../generator/SearchResultGenerator.java | 43 ++++ 19 files changed, 1183 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java new file mode 100644 index 0000000000..8e5887d335 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.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.app.rest.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.Motivation; +import de.digitalcollections.iiif.model.openannotation.Annotation; +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the IIIF Presentation API version 2.2.1 domain model. Annotations associate + * content resources and commentary with a canvas. + * + * This facade is provided for serializing AnnotationList and Search query responses. + */ +@Component +@RequestScope +public class AnnotationGenerator implements IIIFResource { + + private String identifier; + private Canvas onCanvas; + private Motivation motivation; + private ContentAsTextGenerator contentAsText; + private ExternalLinksGenerator otherContent; + // Renamed to "partOf" in IIIF v 3.0 + private List within; + + public static final Motivation PAINTING = new Motivation("sc:painting"); + public static final Motivation COMMENTING = new Motivation("oa:commenting"); + public static final Motivation LINKING = new Motivation("oa:linking"); + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public void setMotivation(Motivation motivation) { + this.motivation = motivation; + } + + public void setOnCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + this.onCanvas = (Canvas) canvas.getResource(); + } + + public void setResource(org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator contentAsText) { + this.contentAsText = contentAsText; + } + + public void setResource(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { + this.otherContent = otherContent; + } + + public void setWithin(List within) { + this.within = within; + } + + @Override + public Resource getResource() { + Annotation annotation = new Annotation(identifier, motivation); + if (contentAsText != null) { + annotation.setResource(contentAsText.getResource()); + } + if (otherContent != null) { + annotation.setResource(otherContent.getResource()); + } + if (onCanvas != null) { + annotation.setOn(onCanvas); + } + if (within != null) { + List manifests = new ArrayList<>(); + for (ManifestGenerator manifest : within) { + manifests.add(manifest.getResource()); + } + annotation.setWithin(manifests); + } + within = null; + identifier = null; + otherContent = null; + onCanvas = null; + return annotation; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java new file mode 100644 index 0000000000..4c76b4131c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java @@ -0,0 +1,43 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.model.generator; + +import de.digitalcollections.iiif.model.openannotation.Annotation; +import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * An ordered list of annotations. Annotation Lists are separate resources that + * should be dereferenced when encountered. + * + * This class is used when retrieving an AnnotationList referenced in the Manifest. + */ +@Component +@Scope("prototype") +public class AnnotationListGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { + + private String identifier; + private Annotation annotation; + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public void addResource(org.dspace.app.rest.iiif.model.generator.AnnotationGenerator annotation) { + this.annotation = (Annotation) annotation.getResource(); + } + + @Override + public Resource getResource() { + AnnotationList annotations = new AnnotationList(identifier); + annotations.addResource(annotation); + return annotations; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java new file mode 100644 index 0000000000..7ea76e8026 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.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.iiif.model.generator; + +import de.digitalcollections.iiif.model.enums.ViewingHint; +import org.springframework.stereotype.Component; + +/** + * IIIF Presentation API 2.1.1 ViewingHint is a hint to the client as to the most appropriate method of + * displaying the resource. This class wraps the ViewingHint enum from the domain model. + * + * With IIIF Presentation API 3.0 the viewingHint property is renamed to "behavior". + */ +@Component +public class BehaviorGenerator implements IIIFValue { + + private String type; + + public void setType(String type) { + this.type = type; + } + + @Override + public ViewingHint getValue() { + if (type == null) { + throw new RuntimeException("Type must be provided for viewing hint."); + } + return new ViewingHint(type); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java new file mode 100644 index 0000000000..f4f611f21d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.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.app.rest.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.ImageContent; +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Facade for Presentation API version 2.1.1 Canvas model. + * + * Changes a Presentation API version 3.0 will likely require new fields for + * this class to support multiple media types. + */ +@Component +@Scope("prototype") +public class CanvasGenerator implements IIIFResource { + + String identifier; + String label; + Integer height; + Integer width; + List> imageContent = new ArrayList<>(); + Resource thumbContent; + + /** + * Canvases must be identified by a URI and it must be an HTTP(s) URI. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Every canvas must have a label to display. + * @param label + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Every canvas must have an integer height. + * @param height + */ + public void setHeight(int height) { + this.height = height; + } + + /** + * Every canvas must have an integer width. + * @param width + */ + public void setWidth(int width) { + this.width = width; + } + + /** + * The ImageContent resource to be assigned to the canvas. + * @param imageContent + */ + public void addImage(Resource imageContent) { + this.imageContent.add(imageContent); + } + + /** + * The ImageContent resource to be assigned as the thumbnail the canvas. + * @param thumbnail + */ + public void addThumbnail(ImageContentGenerator thumbnail) { + this.thumbContent = thumbnail.getResource(); + } + + @Override + public Resource getResource() { + /** + * The Canvas resource typically includes image content. + */ + Canvas canvas; + if (identifier == null) { + throw new RuntimeException("The Canvas resource requires an identifier."); + } + if (label != null) { + canvas = new Canvas(identifier, label); + } else { + canvas = new Canvas(identifier); + } + if (imageContent.size() > 0) { + if (height == null || width == null) { + throw new RuntimeException("The Canvas resource requires both height and width dimensions."); + } + canvas.setWidth(width); + canvas.setHeight(height); + for (Resource res : imageContent) { + canvas.addImage((ImageContent) res); + } + if (thumbContent != null) { + canvas.addThumbnail((ImageContent) thumbContent); + } + } + // Reset properties after each use. + identifier = null; + imageContent.clear(); + label = null; + + return canvas; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java new file mode 100644 index 0000000000..a31d487f63 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.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.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.OtherContent; +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import de.digitalcollections.iiif.model.sharedcanvas.Sequence; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the current Presentation API version 2.1.1 domain model's Sequence class. + * + * In Presentation API version 2.1.1, each Manifest includes a single Sequence that defines + * the order of the views of the object. + * + * Sequence is removed with Presentation API version 3.0. Canvases are added to the Manifest + * items property instead. + */ +@Component +@RequestScope +public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { + + private String identifier; + private OtherContent rendering; + private final List canvas = new ArrayList<>(); + + @Autowired + org.dspace.app.rest.iiif.model.generator.BehaviorGenerator viewingHintFascade; + + /** + * Mandatory. The domain model requires a URI identifier for the sequence. + * @param identifier string for the URI + */ + public void setIdentifier(String identifier) { + + this.identifier = identifier; + } + + /** + * A link to an external resource intended for display or download by a human user. + * This is typically going to be a PDF file. + * @param otherContent wrapper for OtherContent + */ + public void addRendering(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { + + this.rendering = (OtherContent) otherContent.getResource(); + } + + /** + * Add a Canvas to the sequence. + * @param canvas wrapper for Canvas + */ + public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + + this.canvas.add((Canvas) canvas.getResource()); + } + + @Override + public Resource getResource() { + Sequence items = new Sequence(identifier); + if (rendering != null) { + items.addRendering(rendering); + } + items.setCanvases(canvas); + return items; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java new file mode 100644 index 0000000000..0c5c9039d0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import de.digitalcollections.iiif.model.openannotation.ContentAsText; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.stereotype.Component; + +@Component +public class ContentAsTextGenerator implements IIIFResource { + + private String text; + + public void setText(String text) { + this.text = text; + } + @Override + public Resource getResource() { + if (text == null) { + throw new RuntimeException("ContextAsText requires text input."); + } + return new ContentAsText(text); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java new file mode 100644 index 0000000000..9876ff0bd4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.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.iiif.model.generator; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import de.digitalcollections.iiif.model.Profile; +import de.digitalcollections.iiif.model.Service; +import de.digitalcollections.iiif.model.search.ContentSearchService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the Search API version 1.0 search service description. + * + * Added to the Manifest of items that support full-text searching, identified + * by the "relationship.type: IIIFSearchable" DSpace metadata field. + */ +@Component +@RequestScope +public class ContentSearchGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFService { + + private String identifier; + private String label; + + @Autowired + org.dspace.app.rest.iiif.model.generator.ProfileGenerator profile; + + /** + * Mandatory URI for search service. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Optional label + * @param label + */ + public void setLabel(String label) { + this.label = label; + } + + @Override + public Service getService() { + if (identifier == null) { + throw new RuntimeException("You must provide an identifier for the search service."); + } + ContentSearchService contentSearchService = new ContentSearchService(identifier); + if (label != null) { + contentSearchService.setLabel(label); + } + try { + contentSearchService.setContext(new URI("http://iiif.io/api/search/0/context.json")); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + ArrayList profiles = new ArrayList<>(); + profile.setIdentifier("http://iiif.io/api/search/0/search"); + profiles.add(profile.getValue()); + contentSearchService.setProfiles(profiles); + return contentSearchService; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java new file mode 100644 index 0000000000..0ad003b12b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.model.generator; + +import de.digitalcollections.iiif.model.OtherContent; +import de.digitalcollections.iiif.model.PropertyValue; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Facade for the IIIF Presentation API version 2.1.1 "OtherContent" domain model class. + * + * This is the type for Content resources such as images or texts that are associated with a canvas. + * Used in the "related", "renderings" and "otherContent" fields of IIIF resources. + * + * IIIF Presentation API version 3.0 removes the otherContent property and uses annotations + * and items instead. + */ +@Component +public class ExternalLinksGenerator implements IIIFResource { + + @Autowired + PropertyValueGenerator propertyValue; + + private String identifier; + private String format; + private PropertyValue label; + private String type; + + /** + * Sets the mandatory identifier. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the optional format. + * @param format + */ + public void setFormat(String format) { + this.format = format; + } + + /** + * Sets the optional label. + * @param label + */ + public void setLabel(String label) { + propertyValue.setPropertyValue(label); + this.label = propertyValue.getValue(); + } + + /** + * Sets the optional type. + * @param type + */ + public void setType(String type) { + this.type = type; + } + + @Override + public Resource getResource() { + OtherContent otherContent; + if (format != null) { + otherContent = new OtherContent(identifier, format); + } else { + otherContent = new OtherContent(identifier); + } + if (label != null) { + otherContent.setLabel(label); + } + if (type != null) { + otherContent.setType(type); + } + + // Reset facade properties after creating the resource. + identifier = null; + format = null; + label = null; + type = null; + + return otherContent; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java new file mode 100644 index 0000000000..c752e5f866 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java @@ -0,0 +1,20 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import de.digitalcollections.iiif.model.sharedcanvas.Resource; + +public interface IIIFResource { + + /** + * Returns a Resource for serialization. + * @return + */ + Resource getResource(); + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java new file mode 100644 index 0000000000..24d70de119 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java @@ -0,0 +1,19 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import de.digitalcollections.iiif.model.Service; + +public interface IIIFService { + + /** + * Returns a Service for serialization. + * @return + */ + Service getService(); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java new file mode 100644 index 0000000000..2e0cc120bd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.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.app.rest.iiif.model.generator; + +public interface IIIFValue { + + /** + * Returns a value object. + * @return + */ + Object getValue(); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java new file mode 100644 index 0000000000..d8ab207dd4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.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.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.ImageContent; +import de.digitalcollections.iiif.model.Service; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the domain model's ImageContent. + * + * Presentation API version 2.1.1: The ImageContent entity is contained in the "resource" + * field of annotations with motivation "sc:painting". Image resources, and only image resources, + * are included in the images property of the canvas. This changes in API version 3.0. + */ +@Component +@RequestScope +public class ImageContentGenerator implements IIIFResource { + + private String identifier; + private String mimetype; + private org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator imageService; + + /** + * Sets the mandatory identifier for image content. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the optional mimetype. + * @param mimetype + */ + public void setFormat(String mimetype) { + this.mimetype = mimetype; + } + + /** + * Sets the image service that the client will use to retrieve images. + * @param imageService + */ + public void addService(org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator imageService) { + this.imageService = imageService; + } + + @Override + public Resource getResource() { + if (identifier == null) { + throw new RuntimeException("The ImageContent resource requires an identifier."); + } + ImageContent imageContent = new ImageContent(identifier); + if (mimetype != null) { + imageContent.setFormat(mimetype); + } + // Supporting a single service for each image resource. + List services = new ArrayList<>(); + services.add(imageService.getService()); + imageContent.setServices(services); + + return imageContent; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java new file mode 100644 index 0000000000..cac56d0044 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java @@ -0,0 +1,56 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import java.util.ArrayList; + +import de.digitalcollections.iiif.model.Profile; +import de.digitalcollections.iiif.model.Service; +import de.digitalcollections.iiif.model.image.ImageService; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade or Presentation API version 2.1.1 image service property that is added to + * each image resource. + */ +@Component +@RequestScope +public class ImageServiceGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFService { + + private String identifier; + private Profile profile; + + /** + * Sets the mandatory identifier for the image service. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the IIIF image profile. + * @param profile + */ + public void setProfile(org.dspace.app.rest.iiif.model.generator.ProfileGenerator profile) { + this.profile = profile.getValue(); + + } + + @Override + public Service getService() { + ImageService imageService = new ImageService(identifier); + if (profile != null) { + ArrayList profiles = new ArrayList<>(); + profiles.add(profile); + imageService.setProfiles(profiles); + } + return imageService; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java new file mode 100644 index 0000000000..9022a7c960 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -0,0 +1,217 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.model.generator; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.ImageContent; +import de.digitalcollections.iiif.model.MetadataEntry; +import de.digitalcollections.iiif.model.OtherContent; +import de.digitalcollections.iiif.model.PropertyValue; +import de.digitalcollections.iiif.model.enums.ViewingHint; +import de.digitalcollections.iiif.model.search.ContentSearchService; +import de.digitalcollections.iiif.model.sharedcanvas.Manifest; +import de.digitalcollections.iiif.model.sharedcanvas.Range; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import de.digitalcollections.iiif.model.sharedcanvas.Sequence; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the IIIF Presentation API version 2.2.1 domain model. + * + * The Manifest is an overall description of the structure and properties of the digital representation + * of an object. It carries information needed for the viewer to present the digitized content to the user, + * such as a title and other descriptive information about the object or the intellectual work that + * it conveys. Each manifest describes how to present a single object such as a book, a photograph, + * or a statue. + */ +@Component +@RequestScope +public class ManifestGenerator implements IIIFResource { + + private String identifier; + private String label; + private OtherContent seeAlso; + // Becomes the "items" element in IIIF version 3.0 + private CanvasItemsGenerator sequence; + // Renamed to "homepage" in IIIF version 3.0 + private OtherContent related; + // Renamed to "behavior" with IIIF version 3.0 + private ViewingHint viewingHint; + private Resource thumbnail; + private ContentSearchService searchService; + private PropertyValue description; + private final List metadata = new ArrayList<>(); + // Renamed to "rights" with IIIF version 3.0 + private final List license = new ArrayList<>(); + private List ranges = new ArrayList<>(); + + @Autowired + org.dspace.app.rest.iiif.model.generator.PropertyValueGenerator propertyValue; + + @Autowired + org.dspace.app.rest.iiif.model.generator.MetadataEntryGenerator metadataEntry; + + @Autowired + BehaviorGenerator behaviorFascade; + + /** + * Sets the mandatory Manifest ID. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the manditory Manifest label. + * @param label + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Sets the behavior. A hint to the client as to the most appropriate method of displaying the resource + * In IIIF Presentation API version 3.0 semantics this is the "behavior" + * @param viewingHint + */ + public void addViewingHint(String viewingHint) { + behaviorFascade.setType(viewingHint); + this.viewingHint = behaviorFascade.getValue(); + } + + /** + * Use to add single mandatory sequence to Manifest. In IIIF Presentation API 3.0 "sequence" + * is replaced by "items" + * @param sequence + */ + public void addSequence(CanvasItemsGenerator sequence) { + this.sequence = sequence; + } + + /** + * Add otional seeAlso element to Manifest. + * @param seeAlso + */ + public void addSeeAlso(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator seeAlso) { + this.seeAlso = (OtherContent) seeAlso.getResource(); + } + + /** + * Add optional thumbnail image to manifest. + * @param thumbnail + */ + public void addThumbnail(ImageContentGenerator thumbnail) { + this.thumbnail = thumbnail.getResource(); + } + + /** + * Add optional related element to Manifest. + * @param related + */ + public void addRelated(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator related) { + this.related = (OtherContent) related.getResource(); + } + + /** + * Adds optional search service to Manifest. + * @param searchService + */ + public void addService(org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator searchService) { + this.searchService = (ContentSearchService) searchService.getService(); + } + + /** + * Adds single metadata field to Manifest. + * @param field + * @param value + */ + public void addMetadata(String field, String value) { + metadataEntry.setField(field); + metadataEntry.setValue(value); + metadata.add(metadataEntry.getValue()); + } + + /** + * Adds optional license to Manifest. + * @param license + */ + public void addLicense(String license) { + this.license.add(URI.create(license)); + } + + /** + * Adds optional description to Manifest. + * @param field + * @param value + */ + public void addDescription(String field, String value) { + propertyValue.setPropertyValue(field, value); + description = propertyValue.getValue(); + } + + /** + * Adds optional Range to the manifest's structures element. + * @param range + */ + public void addRange(RangeGenerator range) { + ranges.add((Range) range.getResource()); + } + + @Override + public Resource getResource() { + if (identifier == null) { + throw new RuntimeException("The Manifest resource requires an identifier."); + } + Manifest manifest; + if (label != null) { + manifest = new Manifest(identifier, label); + } else { + manifest = new Manifest(identifier); + } + if (sequence != null) { + manifest.addSequence((Sequence) sequence.getResource()); + } + if (ranges.size() > 0) { + manifest.setRanges(ranges); + } + if (metadata.size() > 0) { + for (MetadataEntry meta : metadata) { + manifest.addMetadata(meta); + } + } + if (seeAlso != null) { + manifest.addSeeAlso(seeAlso); + } + if (related != null) { + manifest.addRelated(related); + } + if (searchService != null) { + manifest.addService(searchService); + } + if (license.size() > 0) { + manifest.setLicenses(license); + } + if (description != null) { + manifest.setDescription(description); + } + if (thumbnail != null) { + manifest.addThumbnail((ImageContent) thumbnail); + } + if (viewingHint != null) { + manifest.addViewingHint(viewingHint); + } + return manifest; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java new file mode 100644 index 0000000000..57dccadfa7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.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.app.rest.iiif.model.generator; + +import de.digitalcollections.iiif.model.MetadataEntry; +import org.springframework.stereotype.Component; + +@Component +public class MetadataEntryGenerator implements IIIFValue { + + private String field; + private String value; + + public void setField(String field) { + this.field = field; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public MetadataEntry getValue() { + return new MetadataEntry(field, value); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java new file mode 100644 index 0000000000..6b7e079d0b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.generator; + +import java.net.URI; +import java.net.URISyntaxException; + +import de.digitalcollections.iiif.model.Profile; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class ProfileGenerator implements IIIFValue { + + private String identifier; + /** + * Input String will be converted to URI for use in the Profile. + * @param identifier URI as string + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + @Override + public Profile getValue() { + try { + return new Profile(new URI(identifier)); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java new file mode 100644 index 0000000000..af90628dc1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.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.iiif.model.generator; + +import de.digitalcollections.iiif.model.PropertyValue; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Supports single value PropertyValues. + */ +@Component +@Scope("prototype") +public class PropertyValueGenerator implements IIIFValue { + + private PropertyValue propertyValue; + + public void setPropertyValue(String label) { + propertyValue = new PropertyValue(label); + } + + public void setPropertyValue(String val1, String val2) { + propertyValue = new PropertyValue(val1, val2); + } + + @Override + public PropertyValue getValue() { + return propertyValue; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java new file mode 100644 index 0000000000..4c8c568953 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.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.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Range; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * In Presentation API version 2.1.1, adding a range to the manifest allows the client to display a structured + * hierarchy to enable the user to navigate within the object without merely stepping through the current sequence. + * The rationale for separating ranges from sequences is that there is likely to be overlap between different ranges, + * such as the physical structure of a book compared to the textual structure of the work. + * + * This is used to populate the "structures" element of the Manifest. (The REST API service looks to the "info.json" + * file for ranges.) + */ +@Component +@Scope("prototype") +public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { + + private String identifier; + private String label; + private final List canvasList = new ArrayList<>(); + + /** + * Sets mandatory range identifier. + * @param identifier + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets mandatory range label. + * @param label + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Adds canvas to Range canvas list. + * @param canvas + */ + public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + canvasList.add((Canvas) canvas.getResource()); + } + + @Override + public Resource getResource() { + Range range = new Range(identifier, label); + for (Canvas canvas : canvasList) { + range.addCanvas(canvas); + } + // Reset properties after each use + identifier = null; + canvasList.clear(); + label = null; + + return range; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java new file mode 100644 index 0000000000..289005ec7b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java @@ -0,0 +1,43 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.model.generator; + +import java.util.ArrayList; +import java.util.List; + +import de.digitalcollections.iiif.model.openannotation.Annotation; +import de.digitalcollections.iiif.model.search.SearchResult; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Facade for the AnnotationList that contains hits for a given search query. + */ +@Component +@RequestScope +public class SearchResultGenerator implements IIIFResource { + + private String identifier; + private final List annotations = new ArrayList<>(); + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public void addResource(AnnotationGenerator annotation) { + annotations.add((Annotation) annotation.getResource()); + } + + @Override + public Resource getResource() { + SearchResult searchResult = new SearchResult(identifier); + searchResult.setResources(annotations); + return searchResult; + } +} From c746a9b43bbe75407a9411e355c9d3edb1e7ca34 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:47:15 -0700 Subject: [PATCH 0049/1254] Added factory for objectmapper. --- .../rest/iiif/model/ObjectMapperFactory.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java new file mode 100644 index 0000000000..1b5bb782c0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java @@ -0,0 +1,26 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import de.digitalcollections.iiif.model.jackson.IiifModule; +import de.digitalcollections.iiif.model.jackson.IiifObjectMapper; + +public class ObjectMapperFactory { + + private ObjectMapperFactory() {} + + public static ObjectMapper getIiifObjectMapper() { + return new IiifObjectMapper(); + } + + public static SimpleModule getIiifModule() { + return new IiifModule(); + } +} From 86bf766db16fe98b9b5d51cc2eea9a39d63a79a1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:48:31 -0700 Subject: [PATCH 0050/1254] Added classes for parsing the info.json object. --- .../rest/iiif/model/info/AnnotationModel.java | 30 +++++++++++ .../app/rest/iiif/model/info/CanvasModel.java | 50 ++++++++++++++++++ .../rest/iiif/model/info/GlobalDefaults.java | 51 +++++++++++++++++++ .../dspace/app/rest/iiif/model/info/Info.java | 42 +++++++++++++++ .../app/rest/iiif/model/info/RangeModel.java | 31 +++++++++++ 5 files changed, 204 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.java new file mode 100644 index 0000000000..3bfcf5efca --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.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.iiif.model.info; + +public class AnnotationModel { + + private String motivation; + private String id; + + public void setMotivation(String motivation) { + this.motivation = motivation; + } + + public String getMotivation() { + return motivation; + } + + public void setID(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.java new file mode 100644 index 0000000000..8dc6cac0ca --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.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.iiif.model.info; + +public class CanvasModel { + + private String label; + private int width; + private int height; + private int pos; + + public void setLabel(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getWidth() { + return width; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getHeight() { + return height; + } + + // TODO: These can be removed. + public void setPos(int pos) { + this.pos = pos; + } + + public int getPos() { + return pos; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java new file mode 100644 index 0000000000..b8455e6332 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.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.iiif.model.info; + +public class GlobalDefaults { + + private boolean activated; + private String label; + private int width; + private int height; + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java new file mode 100644 index 0000000000..898576639f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java @@ -0,0 +1,42 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.model.info; + +import java.util.List; + +public class Info { + + private List canvases; + private List structures; + private GlobalDefaults globalDefaults; + + public GlobalDefaults getGlobalDefaults() { + return globalDefaults; + } + + public void setGlobalDefaults(GlobalDefaults globalDefaults) { + this.globalDefaults = globalDefaults; + } + + public void setCanvases(List canvases) { + this.canvases = canvases; + } + + public List getCanvases() { + return this.canvases; + } + + public void setStructures(List structures) { + this.structures = structures; + } + + public List getStructures() { + return structures; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.java new file mode 100644 index 0000000000..b1ef0503bf --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.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.app.rest.iiif.model.info; + +public class RangeModel { + + private String label; + private int start; + + public void setLabel(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public void setStart(int start) { + this.start = start; + } + + public int getStart() { + return start; + } + +} From ffdf6de26f11c943987a6c6f4a547e34ecc63499 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:52:21 -0700 Subject: [PATCH 0051/1254] Added iiif service and utility classes. --- .../iiif/service/AbstractResourceService.java | 157 ++++++++ .../iiif/service/AnnotationListService.java | 121 +++++++ .../iiif/service/CanvasLookupService.java | 59 +++ .../app/rest/iiif/service/CanvasService.java | 93 +++++ .../rest/iiif/service/ManifestService.java | 338 ++++++++++++++++++ .../app/rest/iiif/service/SearchService.java | 213 +++++++++++ .../app/rest/iiif/service/util/IIIFUtils.java | 314 ++++++++++++++++ .../iiif/service/util/ImageProfileUtil.java | 33 ++ .../iiif/service/util/ThumbProfileUtil.java | 34 ++ 9 files changed, 1362 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java new file mode 100644 index 0000000000..6b87cae231 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -0,0 +1,157 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.util.UUID; + +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator; +import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.rest.iiif.service.util.ImageProfileUtil; +import org.dspace.app.rest.iiif.service.util.ThumbProfileUtil; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Base class for IIIF responses. + */ +public abstract class AbstractResourceService { + + /** + * These values are defined in dspace configuration. + */ + protected String IIIF_ENDPOINT; + protected String IMAGE_SERVICE; + protected String SEARCH_URL; + protected String CLIENT_URL; + protected String BITSTREAM_PATH_PREFIX; + /** + * Possible values: "paged" or "individuals". Typically paged is preferred + * for documents. However, it can be overridden in configuration if necessary + * for the viewer client. + */ + protected static String DOCUMENT_VIEWING_HINT; + + // TODO: should these bundle settings be added to dspace configuration or hard-coded here? + // The DSpace bundle used for IIIF entity types. + protected static final String IIIF_BUNDLE = "IIIF"; + // The DSpace bundle for other content related to item. + protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; + + // Paths for IIIF Image API requests. + protected static final String THUMBNAIL_PATH = "/full/,90/0/default.jpg"; + protected static final String IMAGE_PATH = "/full/full/0/default.jpg"; + + @Autowired + IIIFUtils utils; + + @Autowired + ThumbProfileUtil thumbUtil; + + @Autowired + ImageProfileUtil imageUtil; + + @Autowired + ImageContentGenerator imageContent; + + @Autowired + ImageServiceGenerator imageService; + + /** + * Set constants using DSpace configuration definitions. + * @param configurationService the DSpace configuration service + */ + protected void setConfiguration(ConfigurationService configurationService) { + IIIF_ENDPOINT = configurationService.getProperty("iiif.url"); + IMAGE_SERVICE = configurationService.getProperty("iiif.image.server"); + SEARCH_URL = configurationService.getProperty("iiif.solr.search.url"); + BITSTREAM_PATH_PREFIX = configurationService.getProperty("iiif.bitstream.url"); + DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint"); + CLIENT_URL = configurationService.getProperty("dspace.ui.url"); + } + + /** + * Creates the manifest id from the provided uuid. + * @param uuid the item id + * @return the manifest identifier (url) + */ + protected String getManifestId(UUID uuid) { + return IIIF_ENDPOINT + uuid + "/manifest"; + } + + /** + * Association of images with their respective canvases is done via annotations. + * Only the annotations that associate images or parts of images are included in + * the canvas in the images property. If a IIIF Image API service is available for + * the image, then a link to the service’s base URI should be included. + * + * This method adds an image annotations to a canvas for both thumbnail and full size + * images. The annotation references the IIIF image service. + * + * @param canvas the Canvas object. + * @param mimeType the image mime type + * @param bitstreamID the bitstream uuid + */ + protected void addImage(CanvasGenerator canvas, String mimeType, UUID bitstreamID) throws + RuntimeException { + canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); + // Add image content resource to canvas facade. + canvas.addImage(getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH).getResource()); + } + + /** + * A small image that depicts or pictorially represents the resource that + * the property is attached to, such as the title page, a significant image + * or rendering of a canvas with multiple content resources associated with it. + * It is recommended that a IIIF Image API service be available for this image for + * manipulations such as resizing. + * + * This method returns a thumbnail annotation that includes the IIIF image service. + * + * @param uuid the bitstream id + * @return thumbnail Annotation + */ + protected ImageContentGenerator getThumbnailAnnotation(UUID uuid, String mimetype) throws + RuntimeException { + return getImageContent(uuid, mimetype, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); + } + + /** + * Association of images with their respective canvases is done via annotations. The Open Annotation model + * allows any resource to be associated with any other resource, or parts thereof, and it is reused for + * both commentary and painting resources on the canvas. + * @param uuid bitstream uuid + * @param mimetype bitstream mimetype + * @param profile the service profile + * @param path the path component of the identifier + * @return + */ + private ImageContentGenerator getImageContent(UUID uuid, String mimetype, ProfileGenerator profile, String path) { + imageContent.setFormat(mimetype); + imageContent.setIdentifier(IMAGE_SERVICE + uuid + path); + imageContent.addService(getImageService(profile, uuid.toString())); + return imageContent; + } + + /** + * A link to a service that makes more functionality available for the resource, + * such as from an image to the base URI of an associated IIIF Image API service. + * + * @param profile service profile + * @param uuid id of the image bitstream + * @return object representing the Image Service + */ + private ImageServiceGenerator getImageService(ProfileGenerator profile, String uuid) { + imageService.setIdentifier(IMAGE_SERVICE + uuid); + imageService.setProfile(profile); + return imageService; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java new file mode 100644 index 0000000000..43e7caf653 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java @@ -0,0 +1,121 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.rest.iiif.model.generator.AnnotationListGenerator; +import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.rest.iiif.model.generator.PropertyValueGenerator; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class AnnotationListService extends AbstractResourceService { + + @Autowired + IIIFUtils utils; + + @Autowired + ItemService itemService; + + @Autowired + BitstreamService bitstreamService; + + @Autowired + BitstreamFormatService bitstreamFormatService; + + @Autowired + PropertyValueGenerator propertyValue; + + @Autowired + AnnotationListGenerator annotationList; + + @Autowired + AnnotationGenerator annotation; + + @Autowired + ExternalLinksGenerator otherContent; + + public AnnotationListService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Returns an AnnotationList for bitstreams in the OtherContent bundle. + * These resources are not appended directly to the manifest but can be accessed + * via the seeAlso link. + * + * The semantics of this linking property may be extended to full text files, but + * machine readable formats like ALTO, METS, and schema.org descriptions are preferred. + * + * @param context DSpace context + * @param id bitstream uuid + * @return AnnotationList as JSON + */ + public String getSeeAlsoAnnotations(Context context, UUID id) + throws RuntimeException { + /** + * We need the DSpace item to proceed. + */ + Item item; + try { + item = itemService.find(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + /** + * AnnotationList requires an identifier. + */ + annotationList.setIdentifier(IIIF_ENDPOINT + id + "/manifest/seeAlso"); + /** + * Get the "OtherContent" bundle for the item. Then add + * Annotations for each bitstream found in the bundle. + */ + List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); + if (bundles.size() > 0) { + for (Bundle bundle : bundles) { + List bitstreams = bundle.getBitstreams(); + for (Bitstream bitstream : bitstreams) { + BitstreamFormat format; + String mimetype; + try { + format = bitstream.getFormat(context); + mimetype = format.getMIMEType(); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + annotation.setIdentifier(IIIF_ENDPOINT + bitstream.getID() + "/annot"); + annotation.setMotivation(AnnotationGenerator.LINKING); + otherContent.setIdentifier(BITSTREAM_PATH_PREFIX + + "/" + + bitstream.getID() + + "/content"); + otherContent.setFormat(mimetype); + otherContent.setLabel(bitstream.getName()); + annotation.setResource(otherContent); + annotationList.addResource(annotation); + } + } + } + return utils.asJson(annotationList.getResource()); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java new file mode 100644 index 0000000000..186a3de7cb --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.service; + +import java.util.ArrayList; +import java.util.UUID; + +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.stereotype.Component; + +/** + * Canvases may be dereferenced separately from the manifest via their URIs. + */ +@Component +public class CanvasLookupService extends AbstractResourceService { + + @Autowired + IIIFUtils utils; + + @Autowired + CanvasService canvasService; + + public CanvasLookupService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + public String generateCanvas(Context context, Item item, String canvasId) { + int canvasPosition = utils.getCanvasId(canvasId); + Bitstream bitstream = utils.getBitstreamForCanvas(item, IIIF_BUNDLE, canvasPosition); + if (bitstream == null) { + throw new ResourceNotFoundException(); + } + Info info = + utils.validateInfoForSingleCanvas(utils.getInfo(context, item, IIIF_BUNDLE), canvasPosition); + ArrayList bitstreams = new ArrayList<>(); + bitstreams.add(bitstream); + UUID bitstreamID = bitstream.getID(); + String mimeType = utils.getBitstreamMimeType(bitstream, context); + CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, canvasPosition); + if (mimeType.contains("image/")) { + addImage(canvas, mimeType, bitstreamID); + } + return utils.asJson(canvas.getResource()); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java new file mode 100644 index 0000000000..fe1f30427a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class CanvasService extends AbstractResourceService { + + private static final Logger log = Logger.getLogger(CanvasService.class); + + // Default canvas dimensions. + protected static final Integer DEFAULT_CANVAS_WIDTH = 1200; + protected static final Integer DEFAULT_CANVAS_HEIGHT = 1600; + + @Autowired + CanvasGenerator canvas; + + /** + * Constructor. + * @param configurationService the DSpace configuration service. + */ + public CanvasService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Creates a single Canvas object. If canvas parameters are provided by the + * Info object they are used. If canvas parameters are unavailable, default values + * are used instead. + * + * @param id manifest id + * @param info parameters for this canvas + * @param count the canvas position in the sequence. + * @return canvas object + */ + protected CanvasGenerator getCanvas(String id, Info info, int count) { + // Defaults settings. + int canvasWidth = DEFAULT_CANVAS_WIDTH; + int canvasHeight = DEFAULT_CANVAS_HEIGHT; + int pagePosition = count + 1; + String label = "Page " + pagePosition; + // Override with settings from info.json, if available. + if (info != null && info.getGlobalDefaults() != null && info.getCanvases() != null) { + // Use global settings if activated. + if (info.getGlobalDefaults().isActivated()) { + // Create unique label by appending position to the default label. + label = info.getGlobalDefaults().getLabel() + " " + pagePosition; + canvasWidth = info.getGlobalDefaults().getWidth(); + canvasHeight = info.getGlobalDefaults().getHeight(); + } else if (info.getCanvases().get(count) != null) { + if (info.getCanvases().get(count).getLabel().length() > 0) { + // Individually defined canvas labels assumed unique, and are not incremented. + label = info.getCanvases().get(count).getLabel(); + } + canvasWidth = info.getCanvases().get(count).getWidth(); + canvasHeight = info.getCanvases().get(count).getHeight(); + } + } else { + log.info("Correctly formatted info.json was not found for item. Using application defaults."); + } + canvas.setIdentifier(IIIF_ENDPOINT + id + "/canvas/c" + count); + canvas.setLabel(label); + canvas.setHeight(canvasHeight); + canvas.setWidth(canvasWidth); + return canvas; + } + + /** + * Ranges expect the Canvas object to have only an identifier. This method assures that the + * injected canvas facade is empty before setting the identifier. + * @param identifier the DSpace item identifier + * @param startCanvas the position of the canvas in list + * @return + */ + protected CanvasGenerator getRangeCanvasReference(String identifier, String startCanvas) { + canvas.setIdentifier(IIIF_ENDPOINT + identifier + startCanvas); + return canvas; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java new file mode 100644 index 0000000000..ec63cd750d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -0,0 +1,338 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; +import org.apache.log4j.Logger; +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; +import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; +import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; +import org.dspace.app.rest.iiif.model.generator.RangeGenerator; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.app.rest.iiif.model.info.RangeModel; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Generates IIIF Manifest JSON response for a DSpace Item. + */ +@Component +public class ManifestService extends AbstractResourceService { + + private static final Logger log = Logger.getLogger(ManifestService.class); + + private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; + private static final String RELATED_ITEM_LABEL = "DSpace item view"; + private static final String SEE_ALSO_LABEL = "More descriptions of this resource"; + + @Autowired + protected ItemService itemService; + + @Autowired + CanvasService canvasService; + + @Autowired + ExternalLinksGenerator otherContentGenerator; + + @Autowired + ManifestGenerator manifestGenerator; + + @Autowired + CanvasItemsGenerator sequenceGenerator; + + @Autowired + RangeGenerator rangeGenerator; + + @Autowired + ContentSearchGenerator contentSearchGenerator; + + @Autowired + IIIFUtils utils; + + /** + * Constructor. + * @param configurationService the DSpace configuration service. + */ + public ManifestService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Returns serialized Manifest response for a DSpace item. + * + * @param item the DSpace Item + * @param context the DSpace context + * @return Manifest as JSON + */ + public String getManifest(Item item, Context context) { + initializeManifestGenerator(item, context); + return utils.asJson(manifestGenerator.getResource()); + } + + /** + * Initializes the Manifest for a DSpace item. + * + * @param item DSpace Item + * @param context DSpace context + * @return manifest object + */ + private void initializeManifestGenerator(Item item, Context context) { + List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); + List bitstreams = utils.getBitstreams(bundles); + Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); + manifestGenerator.setIdentifier(getManifestId(item.getID())); + manifestGenerator.setLabel(item.getName()); + addRelated(item); + addSearchService(item); + addMetadata(item.getMetadata()); + addViewingHint(bitstreams.size()); + addThumbnail(bitstreams, context); + addSequence(item, bitstreams, context, info); + addRanges(info, item.getID().toString()); + addSeeAlso(item); + } + + /** + * Returns a single sequence with canvases and item rendering (optional). + * @param item DSpace Item + * @param bitstreams list of bitstreams + * @param context the DSpace context + * @return a sequence of canvases + */ + private void addSequence(Item item, List bitstreams, Context context, Info info) { + sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); + if (bitstreams.size() > 0) { + addCanvas(sequenceGenerator, context, item, bitstreams, info); + } + addRendering(sequenceGenerator, item, context); + manifestGenerator.addSequence(sequenceGenerator); + } + + /** + * Adds DSpace Item metadata to the manifest. + * + * @param metadata list of DSpace metadata values + */ + private void addMetadata(List metadata) { + for (MetadataValue meta : metadata) { + String field = utils.getMetadataFieldName(meta); + if (field.contentEquals("rights.uri")) { + manifestGenerator.addMetadata(field, meta.getValue()); + manifestGenerator.addLicense(meta.getValue()); + } else if (field.contentEquals("description")) { + // Add manifest description field. + manifestGenerator.addDescription(field, meta.getValue()); + } else { + // Exclude DSpace description.provenance field. + if (!field.contentEquals("description.provenance")) { + // Everything else, add to manifest metadata fields. + manifestGenerator.addMetadata(field, meta.getValue()); + } + } + } + } + + /** + * A link to an external resource intended to be displayed directly to the user, + * and is related to the resource that has the related property. Examples might + * include a video or academic paper about the resource, a website, an HTML + * description, and so forth. + * + * This method adds a link to the Item represented in the DSpace Angular UI. + * + * @param item the DSpace Item + */ + private void addRelated(Item item) { + String url = CLIENT_URL + "/items/" + item.getID(); + otherContentGenerator.setIdentifier(url); + otherContentGenerator.setFormat("text/html"); + otherContentGenerator.setLabel(RELATED_ITEM_LABEL); + manifestGenerator.addRelated(otherContentGenerator); + } + + /** + * This method adds a canvas to the sequence for each item in the list of DSpace bitstreams. + * To be added bitstreams must be on image mime type. + * + * @param sequence the sequence object + * @param context the DSpace context + * @param item the DSpace Item + * @param bitstreams list of DSpace bitstreams + */ + private void addCanvas(CanvasItemsGenerator sequence, Context context, Item item, + List bitstreams, Info info) { + /** + * Counter tracks the position of the bitstream in the list and is used to create the canvas identifier. + * Bitstream order is determined by position in the IIIF DSpace bundle. + */ + int counter = 0; + for (Bitstream bitstream : bitstreams) { + UUID bitstreamID = bitstream.getID(); + String mimeType = utils.getBitstreamMimeType(bitstream, context); + if (utils.checkImageMimeType(mimeType)) { + CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, counter); + addImage(canvas, mimeType, bitstreamID); + if (counter == 2) { + addImage(canvas, mimeType, bitstreamID); + } + sequence.addCanvas(canvas); + counter++; + } + } + } + + /** + * A hint to the client as to the most appropriate method of displaying the resource. + * + * @param bitstreamCount count of bitstreams in the IIIF bundle. + */ + private void addViewingHint(int bitstreamCount) { + if (bitstreamCount > 2) { + manifestGenerator.addViewingHint(DOCUMENT_VIEWING_HINT); + } + } + + /** + * A link to a machine readable document that semantically describes the resource with + * the seeAlso property, such as an XML or RDF description. This document could be used + * for search and discovery or inferencing purposes, or just to provide a longer + * description of the resource. May have one or more external descriptions related to it. + * + * This method appends an AnnotationList of resources found in the Item's OtherContent bundle. + * A typical use case would be METS or ALTO files that describe the resource. + * + * @param item the DSpace Item. + */ + private void addSeeAlso(Item item) { + List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); + if (bundles.size() == 0) { + return; + } + otherContentGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso"); + otherContentGenerator.setType(AnnotationList.TYPE); + otherContentGenerator.setLabel(SEE_ALSO_LABEL); + manifestGenerator.addSeeAlso(otherContentGenerator); + } + + /** + * A link to an external resource intended for display or download by a human user. + * This property can be used to link from a manifest, collection or other resource + * to the preferred viewing environment for that resource, such as a viewer page on + * the publisher’s web site. Other uses include a rendering of a manifest as a PDF + * or EPUB. + * + * This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds + * it to the Sequence if found. + * + * @param sequence Sequence object + * @param item DSpace Item + * @param context DSpace context + */ + private void addRendering(CanvasItemsGenerator sequence, Item item, Context context) { + List bundles = item.getBundles("ORIGINAL"); + if (bundles.size() == 0) { + return; + } + Bundle bundle = bundles.get(0); + List bitstreams = bundle.getBitstreams(); + for (Bitstream bitstream : bitstreams) { + String mimeType = null; + try { + mimeType = bitstream.getFormat(context).getMIMEType(); + } catch (SQLException e) { + e.printStackTrace(); + } + // If the ORIGINAL bundle contains a PDF, assume that it represents the + // item and add to rendering. Ignore other mime-types. This convention should + // be documented. + if (mimeType != null && mimeType.contentEquals("application/pdf")) { + String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; + otherContentGenerator.setIdentifier(id); + otherContentGenerator.setLabel(PDF_DOWNLOAD_LABEL); + otherContentGenerator.setFormat(mimeType); + sequence.addRendering(otherContentGenerator); + } + } + } + + /** + * A link to a service that makes more functionality available for the resource, + * such as the base URI of an associated IIIF Search API service. + * + * This method returns a search service definition. Search scope is the manifest. + * + * @param item DSpace Item + * @return the IIIF search service definition for the item + */ + private void addSearchService(Item item) { + if (utils.isSearchable(item)) { + contentSearchGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/search"); + // TODO: get label from configuration then set on generator? + manifestGenerator.addService(contentSearchGenerator); + } + } + + /** + * Adds Ranges to manifest structures element. + * Ranges are defined in the info.json file. + * @param info + * @param identifier + */ + private void addRanges(Info info, String identifier) { + List rangesFromConfig = utils.getRangesFromInfoObject(info); + if (rangesFromConfig != null) { + for (int pos = 0; pos < rangesFromConfig.size(); pos++) { + setRange(identifier, rangesFromConfig.get(pos), pos); + manifestGenerator.addRange(rangeGenerator); + } + } + } + + /** + * Sets properties on the RangeFacade. + * @param identifier DSpace item id + * @param range range from info.json configuration + * @param pos list position of the range + */ + private void setRange(String identifier, RangeModel range, int pos) { + String id = IIIF_ENDPOINT + identifier + "/r" + pos; + String label = range.getLabel(); + rangeGenerator.setIdentifier(id); + rangeGenerator.setLabel(label); + String startCanvas = utils.getCanvasId(range.getStart()); + rangeGenerator.addCanvas(canvasService.getRangeCanvasReference(identifier, startCanvas)); + } + + /** + * Adds thumbnail to the manifest + * @param bitstreams + * @param context + */ + private void addThumbnail(List bitstreams, Context context) { + if (bitstreams.size() > 0) { + String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context); + if (utils.checkImageMimeType(mimeType)) { + manifestGenerator.addThumbnail(getThumbnailAnnotation(bitstreams.get(0).getID(), mimeType)); + } + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java new file mode 100644 index 0000000000..a16e0e32ab --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -0,0 +1,213 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; +import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; +import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Implements IIIF Search API queries and responses. + */ +@Component +@RequestScope +public class SearchService extends AbstractResourceService { + + @Autowired + IIIFUtils utils; + + @Autowired + ContentAsTextGenerator contentAsText; + + @Autowired + CanvasGenerator canvas; + + @Autowired + AnnotationGenerator annotation; + + @Autowired + ManifestGenerator manifest; + + @Autowired + SearchResultGenerator searchResult; + + public SearchService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Executes a search that is scoped to the manifest. + * + * @param uuid the IIIF manifest uuid + * @param query the solr query + * @return IIIF json + */ + public String searchWithinManifest(UUID uuid, String query) { + String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); + String json = getSolrSearchResponse(createSearchUrl(encodedQuery, getManifestId(uuid))); + return getAnnotationList(json, uuid, encodedQuery); + } + + /** + * Executes the Search API solr query. + * @param url solr query url + * @return json query response + */ + private String getSolrSearchResponse(URL url) { + InputStream jsonStream; + String json = null; + try { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Accept", "application/json"); + jsonStream = connection.getInputStream(); + json = IOUtils.toString(jsonStream, StandardCharsets.UTF_8); + } catch (IOException e) { + e.printStackTrace(); + } + return json; + } + + /** + * Constructs a solr search URL. + * + * @param encodedQuery the search terms + * @param manifestId the id of the manifest in which to search + * @return solr query + */ + private URL createSearchUrl(String encodedQuery, String manifestId) { + + String fullQuery = SEARCH_URL + "/select?" + + "q=ocr_text:\"" + encodedQuery + + "\"%20AND%20manifest_url:\"" + manifestId + "\"" + + "&hl=true" + + "&hl.ocr.fl=ocr_text" + + "&hl.ocr.contextBlock=line" + + "&hl.ocr.contextSize=2" + + "&hl.snippets=10" + + "&hl.ocr.limitBlock=page" + + "&hl.ocr.absoluteHighlights=true"; + try { + URL url = new URL(fullQuery); + return url; + } catch (MalformedURLException e) { + throw new RuntimeException("Malformed query URL", e); + } + } + + /** + * Generates a Search API response from the word_highlighting solr query response. + * + * The function assumes that the solr query responses contains page IDs + * (taken from the ALTO Page ID element) in the following format: + * Page.0, Page.1, Page.2.... + * + * The identifier values must be aligned with zero-based IIIF canvas identifiers: + * c0, c1, c2.... + * + * The convention convention for Alto IDs must be followed when indexing ALTO files + * into the word_highlighting solr index. If it is not, search responses will not + * match canvases. + * + * @param json solr search result + * @param uuid DSpace Item uuid + * @param encodedQuery the solr query + * @return a search response in JSON + */ + private String getAnnotationList(String json, UUID uuid, String encodedQuery) { + searchResult.setIdentifier(getManifestId(uuid) + "/search?q=" + encodedQuery); + GsonBuilder builder = new GsonBuilder(); + Gson gson = builder.create(); + JsonObject body = gson.fromJson(json, JsonObject.class); + // outer ocr highlight element + JsonObject highs = body.getAsJsonObject("ocrHighlighting"); + // highlight entries + for (Map.Entry ocrIds: highs.entrySet()) { + // ocr_text + JsonObject ocrObj = ocrIds.getValue().getAsJsonObject().getAsJsonObject("ocr_text"); + // snippets array + if (ocrObj != null) { + for (JsonElement snippetArray : ocrObj.getAsJsonObject().get("snippets").getAsJsonArray()) { + for (JsonElement highlights : snippetArray.getAsJsonObject().getAsJsonArray("highlights")) { + for (JsonElement highlight : highlights.getAsJsonArray()) { + JsonObject hcoords = highlight.getAsJsonObject(); + String text = (hcoords.get("text").getAsString()); + String pageId = getCanvasId((hcoords.get("page").getAsString())); + Integer ulx = hcoords.get("ulx").getAsInt(); + Integer uly = hcoords.get("uly").getAsInt(); + Integer lrx = hcoords.get("lrx").getAsInt(); + Integer lry = hcoords.get("lry").getAsInt(); + String w = Integer.toString(lrx - ulx); + String h = Integer.toString(lry - uly); + String params = ulx + "," + uly + "," + w + "," + h; + AnnotationGenerator annot = createSearchResultAnnotation(params, text, pageId, uuid); + searchResult.addResource(annot); + } + } + } + } + } + return utils.asJson(searchResult.getResource()); + } + + private String getCanvasId(String altoId) { + String[] identArr = altoId.split("\\."); + return "c" + identArr[1]; + } + + /** + * Creates annotation with word highlight coordinates. + * + * @param params word coordinate parameters used for highlighting. + * @param text word text + * @param pageId the page id returned by solr + * @param uuid the dspace item identifier + * @return a single annotation object that contains word highlights on a single page (canvas) + */ + private AnnotationGenerator createSearchResultAnnotation(String params, String text, String pageId, UUID uuid) { + annotation.setIdentifier(IIIF_ENDPOINT + uuid + "/annot/" + pageId + "-" + + params); + canvas.setIdentifier(IIIF_ENDPOINT + uuid + "/canvas/" + pageId + "#xywh=" + + params); + annotation.setOnCanvas(canvas); + contentAsText.setText(text); + annotation.setResource(contentAsText); + annotation.setMotivation(AnnotationGenerator.PAINTING); + List withinList = new ArrayList<>(); + manifest.setIdentifier(getManifestId(uuid)); + manifest.setLabel("Search within manifest."); + withinList.add(manifest); + annotation.setWithin(withinList); + return annotation; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java new file mode 100644 index 0000000000..e41815c445 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -0,0 +1,314 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service.util; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.apache.log4j.Logger; +import org.dspace.app.rest.iiif.model.ObjectMapperFactory; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.app.rest.iiif.model.info.RangeModel; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class IIIFUtils { + + private static final Logger log = Logger.getLogger(IIIFUtils.class); + + // The canvas position will be appended to this string. + private static final String CANVAS_PATH_BASE = "/canvas/c"; + + // get dbmdz module subclass. + protected SimpleModule iiifModule = ObjectMapperFactory.getIiifModule(); + // Use the dbmdz object mapper subclass. + protected ObjectMapper mapper = ObjectMapperFactory.getIiifObjectMapper(); + + @Autowired + protected BitstreamService bitstreamService; + + /** + * For IIIF entities, this method returns the bundle assigned to IIIF + * bitstreams. If the item is not an IIIF entity, the default (ORIGINAL) + * bundle list is returned instead. + * @param item the DSpace item + * @param iiifBundle the name of the IIIF bundle + * @return DSpace bundle + */ + public List getIiifBundle(Item item, String iiifBundle) { + boolean iiif = item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString().contentEquals("relationship_type")) + .anyMatch(m -> m.getValue().contentEquals("IIIF") || + m.getValue().contentEquals("IIIFSearchable")); + List bundles; + if (iiif) { + bundles = item.getBundles(iiifBundle); + } else { + bundles = item.getBundles(); + } + return bundles; + } + + /** + * Returns the requested bundle. + * @param item DSpace item + * @param name bundle name + * @return + */ + public List getBundle(Item item, String name) { + return item.getBundles(name); + } + + /** + * Returns bitstreams for the first bundle in the list. + * @param bundles list of DSpace bundles + * @return list of bitstreams + */ + public List getBitstreams(List bundles) { + if (bundles == null || bundles.size() == 0) { + throw new RuntimeException("Unable to retrieve DSpace bundle for manifest."); + } + return bundles.get(0).getBitstreams(); + } + + /** + * Returns the bitstream mime type + * @param bitstream DSpace bitstream + * @param context DSpace context + * @return mime type + */ + public String getBitstreamMimeType(Bitstream bitstream, Context context) { + try { + BitstreamFormat bitstreamFormat = bitstream.getFormat(context); + return bitstreamFormat.getMIMEType(); + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Checks to see if the item is searchable. Based on the entity type. + * @param item DSpace item + * @return true if searchable + */ + public boolean isSearchable(Item item) { + return item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString().contentEquals("relationship_type")) + .anyMatch(m -> m.getValue().contentEquals("IIIFSearchable")); + } + + /** + * Returns a metadata field name. + * @param meta the DSpace metadata value object + * @return field name as string + */ + public String getMetadataFieldName(MetadataValue meta) { + String element = meta.getMetadataField().getElement(); + String qualifier = meta.getMetadataField().getQualifier(); + // Need to distinguish DC type from DSpace relationship.type. + // Setting element to be the schema name. + if (meta.getMetadataField().getMetadataSchema().getName().contentEquals("relationship")) { + qualifier = element; + element = meta.getMetadataField().getMetadataSchema().getName(); + } + String field = element; + // Add qualifier if defined. + if (qualifier != null) { + field = field + "." + qualifier; + } + return field; + } + + /** + * Retrives a bitstream based on its position in the IIIF bundle. + * @param item DSpace Item + * @param canvasPosition bitstream position + * @return bitstream + */ + public Bitstream getBitstreamForCanvas(Item item, String bundleName, int canvasPosition) { + List bundles = item.getBundles(bundleName); + if (bundles.size() == 0) { + return null; + } + List bitstreams = bundles.get(0).getBitstreams(); + try { + return bitstreams.get(canvasPosition); + } catch (RuntimeException e) { + throw new RuntimeException("The requested canvas is not available", e); + } + } + + /** + * Attempts to find info.json file in the bitstream bundle and convert + * the json into the Info.class domain model for canvas and range parameters. + * @param context DSpace context + * @param bundleName the IIIF bundle + * @return info domain model + */ + public Info getInfo(Context context, Item item, String bundleName) { + Info info = null; + try { + ObjectMapper mapper = new ObjectMapper(); + // Look for expected json file bitstream in bundle. + Bitstream infoBitstream = bitstreamService + .getBitstreamByName(item, bundleName, "info.json"); + if (infoBitstream != null) { + InputStream is = bitstreamService.retrieve(context, infoBitstream); + info = mapper.readValue(is, Info.class); + } + } catch (IOException | SQLException e) { + log.warn("Unable to read info.json file.", e); + } catch (AuthorizeException e) { + log.warn("Not authorized to access info.json file.", e); + } + return info; + } + + /** + * Returns the range parameter List or null + * @param info the parameters model + * @return list of range models + */ + public List getRangesFromInfoObject(Info info) { + if (info != null) { + return info.getStructures(); + } + return null; + } + + /** + * Extracts canvas position from the URL input path. + * @param canvasId e.g. "c12" + * @return the position, e.g. 12 + */ + public int getCanvasId(String canvasId) { + return Integer.parseInt(canvasId.substring(1)); + } + + /** + * Returns the canvas path with position. The path + * returned is partial, not the fully qualified URI. + * @param position position of the bitstream in the DSpace bundle. + * @return partial canvas path. + */ + public String getCanvasId(int position) { + return CANVAS_PATH_BASE + position; + } + + /** + * Convenience method to compare canvas parameter and bitstream list size. + * @param info the parameter model + * @param bitstreams the list of DSpace bitstreams + * @return true if sizes match + */ + public boolean isListSizeMatch(Info info, List bitstreams) { + // If Info is not null then the bitstream bundle contains info.json; exclude + // the file from comparison. + if (info != null && info.getCanvases().size() == bitstreams.size() - 1) { + return true; + } + return false; + } + + /** + * Convenience method verifies that the requested canvas exists in the + * parameters model object. + * @param info parameter model + * @param canvasPosition requested canvas position + * @return true if index is in bounds + */ + public boolean canvasOutOfBounds(Info info, int canvasPosition) { + return canvasPosition < 0 || canvasPosition >= info.getCanvases().size(); + } + + /** + * Validates info.json for a single canvas. + * Unless global settings are being used, when canvas information is not available + * use defaults. The canvas information is defined in the info.json file. + * @param info the information model + * @param position the position of the requested canvas + * @return information model + */ + public Info validateInfoForSingleCanvas(Info info, int position) { + if (info != null && info.getGlobalDefaults() != null) { + if (canvasOutOfBounds(info, position) && !info.getGlobalDefaults().isActivated()) { + log.warn("Canvas for position " + position + " not defined.\n" + + "Ignoring info.json canvas definitions and using defaults. " + + "Any other canvas-level annotations will also be ignored."); + info.setCanvases(new ArrayList<>()); + } + } + return info; + } + + /** + * Unless global settings are being used, when canvas information list size does + * not match the number of bitstreams use defaults. The canvas information is + * defined in the info.json file. + * @param info the information model + * @param bitstreams the list of bitstreams + * @return information model + */ + public Info validateInfoForManifest(Info info, List bitstreams) { + if (info != null && info.getGlobalDefaults() != null) { + if (!isListSizeMatch(info, bitstreams) && !info.getGlobalDefaults().isActivated()) { + log.warn("Mismatch between info.json canvases and DSpace bitstream count.\n" + + "Ignoring info.json canvas definitions and using defaults." + + "Any other canvas-level annotations will also be ignored."); + info.setCanvases(new ArrayList<>()); + } + } + return info; + } + + /** + * Serializes the json response. + * @param resource to be serialized + * @return + */ + public String asJson(Resource resource) { + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.registerModule(iiifModule); + try { + return mapper.writeValueAsString(resource); + } catch (JsonProcessingException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * Tests for image mimetype. Presentation API 2.1.1 canvas supports images only. + * Other media types introduced in version 3. + * @param mimetype + * @return true if an image + */ + public boolean checkImageMimeType(String mimetype) { + if (mimetype != null && mimetype.contains("image/")) { + return true; + } + return false; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java new file mode 100644 index 0000000000..63fa43910e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.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.iiif.service.util; + +import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ImageProfileUtil { + + @Autowired + ProfileGenerator profile; + + /** + * Utility method for obtaining the image service profile. + * Calling from this utility provides a unique instance of the + * autowired property. Necessary because a single canvas resource contains + * both thumbnail and images. + * + * @return image service profile + */ + public ProfileGenerator getImageProfile() throws + RuntimeException { + profile.setIdentifier("http://iiif.io/api/image/2/level1.json"); + return profile; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java new file mode 100644 index 0000000000..58e5dd18b3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java @@ -0,0 +1,34 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service.util; + +import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ThumbProfileUtil { + + @Autowired + ProfileGenerator profile; + + /** + * Utility method for obtaining the thumbnail image service profile. + * Calling from this utility provides a unique instance of the + * autowired property. Necessary because a single canvas resource contains + * both thumbnail and images. + * + * @return the thumbnail service profile + */ + public ProfileGenerator getThumbnailProfile() throws + RuntimeException { + profile.setIdentifier("http://iiif.io/api/image/2/level0.json"); + return profile; + } + +} From eaee79831beb2dc0b3bb6a23ed988c415a869e43 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:53:28 -0700 Subject: [PATCH 0052/1254] Added iiif repository. --- .../app/rest/iiif/IIIFRestRepository.java | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java new file mode 100644 index 0000000000..1ea10fe24e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java @@ -0,0 +1,129 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif; + +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.app.rest.iiif.service.AnnotationListService; +import org.dspace.app.rest.iiif.service.CanvasLookupService; +import org.dspace.app.rest.iiif.service.ManifestService; +import org.dspace.app.rest.iiif.service.SearchService; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Repository for IIIF Presentation and Search API requests. + */ +@Component +public class IIIFRestRepository { + + @Autowired + ItemService itemService; + + @Autowired + BitstreamService bitstreamService; + + @Autowired + ManifestService manifestService; + + @Autowired + SearchService searchService; + + @Autowired + AnnotationListService annotationListService; + + @Autowired + CanvasLookupService canvasLookupService; + + /** + * The manifest response contains sufficient information for the client to initialize itself + * and begin to display something quickly to the user. The manifest resource represents a single + * object and any intellectual work or works embodied within that object. In particular it + * includes the descriptive, rights and linking information for the object. It then embeds + * the sequence(s) of canvases that should be rendered to the user. + * + * Returns manifest for single DSpace item. + * + * @param context DSpace context + * @param id DSpace Item uuid + * @return manifest as JSON + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public String getManifest(Context context, UUID id) + throws ResourceNotFoundException { + Item item; + try { + item = itemService.find(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (item == null) { + throw new ResourceNotFoundException("IIIF manifest for id " + id + " not found"); + } + return manifestService.getManifest(item, context); + } + + /** + * The canvas represents an individual page or view and acts as a central point for + * laying out the different content resources that make up the display. This information + * should be embedded within a sequence. + * + * @param context DSpace context + * @param id DSpace item uuid + * @param canvasId canvas identifier + * @return canvas as JSON + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public String getCanvas(Context context, UUID id, String canvasId) + throws ResourceNotFoundException { + Item item; + try { + item = itemService.find(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (item == null) { + throw new ResourceNotFoundException("IIIF canvas for id " + id + " not found"); + } + return canvasLookupService.generateCanvas(context, item, canvasId); + } + + /** + * Returns search hits and word coordinates as an AnnotationList. + * + * Search scope is a single DSpace item or manifest. + * + * @param id DSpace item uuid + * @param query query terms + * @return AnnotationList as JSON + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public String searchInManifest(UUID id, String query) { + + return searchService.searchWithinManifest(id, query); + } + + /** + * Returns annotations for machine readable metadata that describes the resource. + * + * @param context DSpace context + * @param id the Item uuid + * @return AnnotationList as JSON + */ + @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") + public String getSeeAlsoAnnotations(Context context, UUID id) { + return annotationListService.getSeeAlsoAnnotations(context, id); + } +} From 6ed7bedaa9e5fe0d286435ad53d0c22268494047 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:54:34 -0700 Subject: [PATCH 0053/1254] Added iiif controller. --- .../org/dspace/app/rest/IIIFController.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java new file mode 100644 index 0000000000..64046f883a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java @@ -0,0 +1,107 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import java.util.UUID; + +import org.dspace.app.rest.iiif.IIIFRestRepository; +import org.dspace.app.rest.repository.AbstractDSpaceRestRepository; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for IIIF Presentation and Search API. + */ +@RestController +@RequestMapping("/api/iiif") +public class IIIFController extends AbstractDSpaceRestRepository { + + @Autowired + IIIFRestRepository iiifRestRepository; + + /** + * The manifest response contains sufficient information for the client to initialize + * itself and begin to display something quickly to the user. The manifest resource + * represents a single object and any intellectual work or works embodied within that + * object. In particular it includes the descriptive, rights and linking information + * for the object. It then embeds the sequence(s) of canvases that should be rendered + * to the user. + * + * Called with GET to retrieve the manifest for a single DSpace item. + * + * @param id DSpace Item uuid + * @return manifest as JSON + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest") + public String findOne(@PathVariable UUID id) { + Context context = obtainContext(); + return iiifRestRepository.getManifest(context, id); + } + + /** + * Any resource in the Presentation API may have a search service associated with it. + * The resource determines the scope of the content that will be searched. A service + * associated with a manifest will search all of the annotations on canvases or other + * objects below the manifest, a service associated with a particular range will only + * search the canvases within the range, or a service on a canvas will search only + * annotations on that particular canvas. The URIs for services associated with different + * resources must be different to allow the client to use the correct one for the desired + * scope of the search. + * + * This endpoint for searches within the manifest scope (by DSpace item uuid). + * + * @param id DSpace Item uuid + * @param q query terms + * @return AnnotationList as JSON + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest/search") + public String searchInManifest(@PathVariable UUID id, + @RequestParam(name = "q") String q) { + return iiifRestRepository.searchInManifest(id, q); + } + + /** + * All resources can link to semantic descriptions of themselves via the seeAlso property. + * These could be METS, ALTO, full text, or a schema.org descriptions. + * + * Since there's currently no reliable way to associate "seeAlso" links and individual + * canvases (e.g. associate a single image with its ALTO file) the + * scope is the entire manifest (or DSpace Item). + * + * @param id DSpace Item uuid + * @return AnnotationList as JSON + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest/seeAlso") + public String findSeeAlsoList(@PathVariable UUID id) { + Context context = obtainContext(); + return iiifRestRepository.getSeeAlsoAnnotations(context, id); + } + + /** + * The canvas represents an individual page or view and acts as a central point for + * laying out the different content resources that make up the display. This information + * should be embedded within a sequence. + * + * This endpoint allows canvases to be dereferenced separately from the manifest. This + * is an atypical use case. + * + * @param id DSpace Item uuid + * @param cid canvas identifier + * @return canvas as JSON + */ + @RequestMapping(method = RequestMethod.GET, value = "/{id}/canvas/{cid}") + public String findCanvas(@PathVariable UUID id, @PathVariable String cid) { + Context context = obtainContext(); + return iiifRestRepository.getCanvas(context, id, cid); + } +} From 79bb86a5db70420c8c28f643da00b923f3e1b10b Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 15:56:55 -0700 Subject: [PATCH 0054/1254] Added integration tests. --- .../dspace/app/iiif/IIIFRestRepositoryIT.java | 569 ++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java new file mode 100644 index 0000000000..8a88b21f49 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java @@ -0,0 +1,569 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif; + +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.InputStream; + +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { + + public static final String IIIFBundle = "IIIF"; + + /** + * info.json set for individual canvases. + */ + String info = "{\"globalDefaults\":{\"activated\": false,\"label\": \"\",\"width\": 0,\"height\": 0}," + + "\"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, \"height\": 4220, \"pos\": 0}]" + + ",\"structures\": []}"; + + /** + * info.json with structures and global canvas setting. + */ + String infoWithStructures = "{\"globalDefaults\":" + + "{\"activated\": true,\"label\": \"Global\",\"width\": 2000,\"height\": 3000}," + + "\"canvases\":[]," + + "\"structures\": " + + "[{\"label\":\"Section 1\",\"start\":1}," + + "{\"label\":\"Section 2\",\"start\":2}]" + + "}"; + + /** + * info.json defaulting to global canvas settings. + */ + String globalInfoConfig = "{\"globalDefaults\":{\"activated\": true,\"label\": \"Global\",\"width\": 2000," + + "\"height\": 3000}, \"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, " + + "\"height\": 4220, \"pos\": 0}],\"structures\": []}"; + + @Test + public void missingBundleTest() 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") + .withRelationshipType("IIIFSearchable") + .build(); + context.restoreAuthSystemState(); + // Status 500 + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().is(500)); + + } + + @Test + public void findOneIIIFSearchableEntityTypeIT() 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") + .withRelationshipType("IIIFSearchable") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + context.restoreAuthSystemState(); + + // Default canvas size and label. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.service.profile", is("http://iiif.io/api/search/0/search"))) + .andExpect(jsonPath("$.thumbnail.@id", Matchers.containsString("/iiif/2/" + + bitstream1.getID()))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(1200))) + .andExpect(jsonPath("$.related.@id", + Matchers.containsString("/items/" + publicItem1.getID()))); + } + + @Test + public void findOneIIIFSearchableWithGlobalConfigIT() 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") + .withRelationshipType("IIIFSearchable") + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(this.globalInfoConfig, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, "IIIF") + .withName("info.json") + .withMimeType("application/json") + .build(); + } + + context.restoreAuthSystemState(); + // Expect canvas label, width and height to match bitstream description. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) + .andExpect(jsonPath("$.service").exists()); + } + + @Test + public void findOneIIIFSearchableWithInfoJsonIT() 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") + .withRelationshipType("IIIFSearchable") + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(this.info, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, "IIIF") + .withName("info.json") + .withMimeType("application/json") + .build(); + } + + context.restoreAuthSystemState(); + // Expect canvas label, width and height to match bitstream description. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(3163))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(4220))) + .andExpect(jsonPath("$.service").exists()); + } + + @Test + public void findOneIIIFEntityPagedHintIT() 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") + .withMetadata("dc", "rights", "uri", "https://license.org") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + String bitstreamContent2 = "ThisIsSomeDummyText2"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream2") + .withMimeType("image/jpeg") + .build(); + } + + String bitstreamContent3 = "ThisIsSomeDummyText3"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream3") + .withMimeType("image/jpeg") + .build(); + } + + context.restoreAuthSystemState(); + + // With more than 2 bitstreams in IIIF bundle, the sequence viewing hint should be "paged" + // unless that has been changed in dspace configuration. This test assumes that DSpace + // has been configured to return the "individuals" hint for documents to better support + // search results in Mirador. That is the current dspace.cfg default setting. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.license", is("https://license.org"))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.viewingHint", is("individuals"))) + .andExpect(jsonPath("$.service").doesNotExist()); + + } + + @Test + public void findOneWithStructures() 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") + .withRelationshipType("IIIFSearchable") + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream2") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream3") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(this.infoWithStructures, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, "IIIF") + .withName("info.json") + .withMimeType("application/json") + .build(); + } + + context.restoreAuthSystemState(); + // expect structures elements with label and canvas id. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) + .andExpect(jsonPath("$.structures[1].label", is("Section 2"))) + .andExpect(jsonPath("$.structures[1].canvases[0]", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c2"))) + .andExpect(jsonPath("$.service").exists()); + } + + @Test + public void findOneIIIFEntityTypeIT() 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") + .withMetadata("dc", "rights", "uri", "https://license.org") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.license", is("https://license.org"))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.service").doesNotExist()); + + } + + @Test + public void findOneIIIFWithOtherContentIT() 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") + .withMetadata("dc", "rights", "uri", "https://license.org") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, "OtherContent") + .withName("file.xml") + .withMimeType("application/xml") + .build(); + } + + context.restoreAuthSystemState(); + + // Expect seeAlso annotation list. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.license", is("https://license.org"))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.seeAlso.@type", is("sc:AnnotationList"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.service").doesNotExist()); + + } + + @Test + public void findOneUsingOriginalBundleIgnoreFileIT() 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") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream1") + .withMimeType("application/pdf") + .build(); + } + + context.restoreAuthSystemState(); + + // Image in the ORIGINAL bundle added as canvas; PDF ignored... + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases", Matchers.hasSize(1))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + .andExpect(jsonPath("$.service").doesNotExist()); + } + + + @Test + public void findOneCanvas() 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") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("IMG1") + .withMimeType("image/jpeg") + .build(); + } + context.restoreAuthSystemState(); + + // Single canvas. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/canvas/c0")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@type", is("sc:Canvas"))) + .andExpect(jsonPath("$.images[0].@type", is("oa:Annotation"))); + } + + @Test + public void missingCanvas() 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") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("IMG1") + .withMimeType("image/jpeg") + .build(); + } + context.restoreAuthSystemState(); + // Status 500. The item contains only one bitstream. The item manifest likewise contains one canvas. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/canvas/c2")) + .andExpect(status().is(500)); + } + + @Test + public void getAnnotationListSeeAlso() 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") + .withRelationshipType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("IMG1") + .withMimeType("image/jpeg") + .build(); + } + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder. + createBitstream(context, publicItem1, is, "OtherContent") + .withName("file.xml") + .withMimeType("application/xml") + .build(); + } + + context.restoreAuthSystemState(); + + // Expect seeAlso AnnotationList if the dspace item includes an OtherContent bundle. + getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest/seeAlso")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@type", is("sc:AnnotationList"))) + .andExpect(jsonPath("$.resources[0].@type", is("oa:Annotation"))) + .andExpect(jsonPath("$.resources[0].resource.@id", + Matchers.containsString(bitstream2.getID() + "/content"))); + + } +} From 675e3c0b645db109d00bdbfaba6fdf6bdc46ad56 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 25 Mar 2021 16:04:45 -0700 Subject: [PATCH 0055/1254] Added new method to the test BitstreamBuilder and moved iiif test package. --- .../org/dspace/builder/BitstreamBuilder.java | 33 ++++++++++++++++++- .../{ => rest}/iiif/IIIFRestRepositoryIT.java | 0 2 files changed, 32 insertions(+), 1 deletion(-) rename dspace-server-webapp/src/test/java/org/dspace/app/{ => rest}/iiif/IIIFRestRepositoryIT.java (100%) diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index b8942a17d0..da9eddacd5 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -49,6 +49,12 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return builder.create(context, bundle, is); } + public static BitstreamBuilder createBitstream(Context context, Item item, InputStream is, String bundleName) + throws SQLException, AuthorizeException, IOException { + BitstreamBuilder builder = new BitstreamBuilder(context); + return builder.createInRequestedBundle(context, item, is, bundleName); + } + private BitstreamBuilder create(Context context, Item item, InputStream is) throws SQLException, AuthorizeException, IOException { this.context = context; @@ -70,6 +76,31 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return this; } + private BitstreamBuilder createInRequestedBundle(Context context, Item item, InputStream is, String bundleName) + throws SQLException, AuthorizeException, IOException { + this.context = context; + this.item = item; + + Bundle bundle = getBundleByName(item, bundleName); + + bitstream = bitstreamService.create(context, bundle, is); + + return this; + } + + private Bundle getBundleByName(Item item, String bundleName) throws SQLException, AuthorizeException { + List bundles = itemService.getBundles(item, bundleName); + Bundle targetBundle = null; + + if (bundles.size() < 1) { + // not found, create a new one + targetBundle = bundleService.create(context, item, bundleName); + } else { + // put bitstreams into first bundle + targetBundle = bundles.iterator().next(); + } + return targetBundle; + } public BitstreamBuilder withName(String name) throws SQLException { bitstream.setName(context, name); @@ -168,4 +199,4 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { protected DSpaceObjectService getService() { return bitstreamService; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java similarity index 100% rename from dspace-server-webapp/src/test/java/org/dspace/app/iiif/IIIFRestRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java From 4fcd6c1e41bd5843dc3a547b302f0ad9deaf96e3 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 26 Mar 2021 16:36:54 -0700 Subject: [PATCH 0056/1254] Added sample iiif config to dspace.cfg --- dspace/config/dspace.cfg | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index f4ba5901f7..bc5bc5a7fe 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1497,6 +1497,23 @@ log.report.dir = ${dspace.dir}/log # Take this key (just the UA-XXXXXX-X part) and place it here in this parameter. # google.analytics.key=UA-XXXXXX-X +#### IIIF CONFIGURATION #### +# Base URL of the DSpace iiif API endpoint. +#iiif.url = ${dspace.server.url}/api/iiif/ + +# Base URL of the IIIF image server. +#iiif.image.server = http://localhost:8182/iiif/2/ + +# Base URL of the solr search index (IIIF Search API). +#iiif.solr.search.url = http://localhost:8983/solr/word_highlighting + +#iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams + +# Possible values: "paged" or "individuals". Typically "paged" is preferred +# for documents. However, it can be overridden here if necessary for the +# viewer client. +#iiif.document.viewing.hint = individuals + #---------------------------------------------------------------# #----------------REQUEST ITEM CONFIGURATION---------------------# #---------------------------------------------------------------# From 952c3b603b124e5480fa09c0a06d7d43c2cedab5 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 26 Mar 2021 16:37:45 -0700 Subject: [PATCH 0057/1254] Minor refactor manifest generator --- .../iiif/model/generator/ManifestGenerator.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 9022a7c960..7451114284 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -56,13 +56,13 @@ public class ManifestGenerator implements IIIFResource { private List ranges = new ArrayList<>(); @Autowired - org.dspace.app.rest.iiif.model.generator.PropertyValueGenerator propertyValue; + PropertyValueGenerator propertyValue; @Autowired - org.dspace.app.rest.iiif.model.generator.MetadataEntryGenerator metadataEntry; + MetadataEntryGenerator metadataEntryGenerator; @Autowired - BehaviorGenerator behaviorFascade; + BehaviorGenerator behaviorGenerator; /** * Sets the mandatory Manifest ID. @@ -86,8 +86,8 @@ public class ManifestGenerator implements IIIFResource { * @param viewingHint */ public void addViewingHint(String viewingHint) { - behaviorFascade.setType(viewingHint); - this.viewingHint = behaviorFascade.getValue(); + behaviorGenerator.setType(viewingHint); + this.viewingHint = behaviorGenerator.getValue(); } /** @@ -137,9 +137,9 @@ public class ManifestGenerator implements IIIFResource { * @param value */ public void addMetadata(String field, String value) { - metadataEntry.setField(field); - metadataEntry.setValue(value); - metadata.add(metadataEntry.getValue()); + metadataEntryGenerator.setField(field); + metadataEntryGenerator.setValue(value); + metadata.add(metadataEntryGenerator.getValue()); } /** From cb81aff432988f52b6a57076ec82855d4df7023e Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 26 Mar 2021 17:45:40 -0700 Subject: [PATCH 0058/1254] Updated the image service thumbnail path. --- .../dspace/app/rest/iiif/service/AbstractResourceService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 6b87cae231..c1e33508c3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -46,7 +46,7 @@ public abstract class AbstractResourceService { protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; // Paths for IIIF Image API requests. - protected static final String THUMBNAIL_PATH = "/full/,90/0/default.jpg"; + protected static final String THUMBNAIL_PATH = "/full/90,/0/default.jpg"; protected static final String IMAGE_PATH = "/full/full/0/default.jpg"; @Autowired From 82171dfa6a4cfd6dde5400e9d3c60c1cf19173ab Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 28 Mar 2021 10:25:24 -0700 Subject: [PATCH 0059/1254] Updated dspace.cfg comment. --- dspace/config/dspace.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index bc5bc5a7fe..2588a3a3d5 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1509,9 +1509,9 @@ log.report.dir = ${dspace.dir}/log #iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams -# Possible values: "paged" or "individuals". Typically "paged" is preferred -# for documents. However, it can be overridden here if necessary for the -# viewer client. +# Sets the viewing hint. Possible values: "paged" or "individuals". +# Typically "paged" is preferred for multi-age documents. Use "individuals" +# if you plan to implement the search api. #iiif.document.viewing.hint = individuals #---------------------------------------------------------------# From ec94740a9a0e7eab0198f67081b7242f7fd7788b Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 28 Mar 2021 17:06:49 -0700 Subject: [PATCH 0060/1254] Added exception to search service query. --- .../dspace/app/rest/iiif/service/SearchService.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index a16e0e32ab..fb12fbe277 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -24,6 +24,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; @@ -42,6 +43,8 @@ import org.springframework.web.context.annotation.RequestScope; @RequestScope public class SearchService extends AbstractResourceService { + private static final Logger log = Logger.getLogger(SearchService.class); + @Autowired IIIFUtils utils; @@ -84,7 +87,7 @@ public class SearchService extends AbstractResourceService { */ private String getSolrSearchResponse(URL url) { InputStream jsonStream; - String json = null; + String json; try { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); @@ -92,7 +95,7 @@ public class SearchService extends AbstractResourceService { jsonStream = connection.getInputStream(); json = IOUtils.toString(jsonStream, StandardCharsets.UTF_8); } catch (IOException e) { - e.printStackTrace(); + throw new RuntimeException("Unable to query solr at: " + url, e); } return json; } @@ -114,8 +117,11 @@ public class SearchService extends AbstractResourceService { "&hl.ocr.contextBlock=line" + "&hl.ocr.contextSize=2" + "&hl.snippets=10" + - "&hl.ocr.limitBlock=page" + + // "&hl.ocr.limitBlock=page" + "&hl.ocr.absoluteHighlights=true"; + + log.debug(fullQuery); + try { URL url = new URL(fullQuery); return url; From bf1451cfe0315f5aec29e1b9373d9ac1f6648f6c Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 29 Mar 2021 16:21:21 -0700 Subject: [PATCH 0061/1254] Updated search service to use solr client libraries. --- .../iiif/service/AbstractResourceService.java | 2 + .../app/rest/iiif/service/SearchService.java | 143 +++++++++++------- 2 files changed, 92 insertions(+), 53 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index c1e33508c3..a94803bb9f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.iiif.service; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.UUID; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index fb12fbe277..e1d6c6e5e2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -8,10 +8,6 @@ package org.dspace.app.rest.iiif.service; import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -21,10 +17,18 @@ import java.util.UUID; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import org.apache.commons.io.IOUtils; +import org.apache.commons.validator.routines.UrlValidator; import org.apache.log4j.Logger; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.impl.NoOpResponseParser; +import org.apache.solr.client.solrj.request.QueryRequest; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.util.NamedList; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; @@ -32,6 +36,7 @@ import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @@ -45,6 +50,8 @@ public class SearchService extends AbstractResourceService { private static final Logger log = Logger.getLogger(SearchService.class); + private final boolean validationEnabled; + @Autowired IIIFUtils utils; @@ -65,38 +72,53 @@ public class SearchService extends AbstractResourceService { public SearchService(ConfigurationService configurationService) { setConfiguration(configurationService); + validationEnabled = configurationService + .getBooleanProperty("discovery.solr.url.validation.enabled", true); } /** * Executes a search that is scoped to the manifest. * - * @param uuid the IIIF manifest uuid + * @param uuid dspace item uuid * @param query the solr query * @return IIIF json */ public String searchWithinManifest(UUID uuid, String query) { String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); - String json = getSolrSearchResponse(createSearchUrl(encodedQuery, getManifestId(uuid))); + String json = getSolrSearchResponse(encodedQuery, getManifestId(uuid)); return getAnnotationList(json, uuid, encodedQuery); } /** * Executes the Search API solr query. - * @param url solr query url + * @param encodedQuery encoded query terms + * @param manifestId the iiif manifest id + * * @return json query response */ - private String getSolrSearchResponse(URL url) { - InputStream jsonStream; - String json; - try { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setRequestProperty("Accept", "application/json"); - jsonStream = connection.getInputStream(); - json = IOUtils.toString(jsonStream, StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException("Unable to query solr at: " + url, e); + private String getSolrSearchResponse(String encodedQuery, String manifestId) { + String json = ""; + String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("iiif.solr.search.url"); + UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); + if (urlValidator.isValid(solrService) || this.validationEnabled) { + HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); + solrServer.setBaseURL(solrService); + solrServer.setUseMultiPartPost(true); + SolrQuery solrQuery = getSolrQuery(encodedQuery, manifestId); + QueryRequest req = new QueryRequest(solrQuery); + req.setResponseParser(new NoOpResponseParser("json")); + NamedList resp = null; + try { + resp = solrServer.request(req); + json = (String) resp.get("response"); + } catch (SolrServerException | IOException e) { + throw new RuntimeException("Unable to retrieve search response.", e); + } + } else { + log.error("Error while initializing solr, invalid url: " + solrService); } + return json; } @@ -107,27 +129,20 @@ public class SearchService extends AbstractResourceService { * @param manifestId the id of the manifest in which to search * @return solr query */ - private URL createSearchUrl(String encodedQuery, String manifestId) { + private SolrQuery getSolrQuery(String encodedQuery, String manifestId) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.setQuery("ocr_text:" + encodedQuery + + " AND manifest_url:\"" + manifestId + "\""); + solrQuery.set(CommonParams.WT, "json"); + solrQuery.set("hl", "true"); + solrQuery.set("hl.ocr.fl", "ocr_text"); + solrQuery.set("hl.ocr.contextBlock", "line"); + solrQuery.set("hl.ocr.contextSize", "2"); + solrQuery.set("hl.snippets", "10"); + solrQuery.set("hl.ocr.limitBlock","page"); + solrQuery.set("hl.ocr.absoluteHighlights", "true"); - String fullQuery = SEARCH_URL + "/select?" + - "q=ocr_text:\"" + encodedQuery + - "\"%20AND%20manifest_url:\"" + manifestId + "\"" + - "&hl=true" + - "&hl.ocr.fl=ocr_text" + - "&hl.ocr.contextBlock=line" + - "&hl.ocr.contextSize=2" + - "&hl.snippets=10" + - // "&hl.ocr.limitBlock=page" + - "&hl.ocr.absoluteHighlights=true"; - - log.debug(fullQuery); - - try { - URL url = new URL(fullQuery); - return url; - } catch (MalformedURLException e) { - throw new RuntimeException("Malformed query URL", e); - } + return solrQuery; } /** @@ -154,6 +169,10 @@ public class SearchService extends AbstractResourceService { GsonBuilder builder = new GsonBuilder(); Gson gson = builder.create(); JsonObject body = gson.fromJson(json, JsonObject.class); + if (body == null) { + log.warn("Unable to process json response."); + return utils.asJson(searchResult.getResource()); + } // outer ocr highlight element JsonObject highs = body.getAsJsonObject("ocrHighlighting"); // highlight entries @@ -163,20 +182,10 @@ public class SearchService extends AbstractResourceService { // snippets array if (ocrObj != null) { for (JsonElement snippetArray : ocrObj.getAsJsonObject().get("snippets").getAsJsonArray()) { + String pageId = getCanvasId(snippetArray.getAsJsonObject().get("pages")); for (JsonElement highlights : snippetArray.getAsJsonObject().getAsJsonArray("highlights")) { for (JsonElement highlight : highlights.getAsJsonArray()) { - JsonObject hcoords = highlight.getAsJsonObject(); - String text = (hcoords.get("text").getAsString()); - String pageId = getCanvasId((hcoords.get("page").getAsString())); - Integer ulx = hcoords.get("ulx").getAsInt(); - Integer uly = hcoords.get("uly").getAsInt(); - Integer lrx = hcoords.get("lrx").getAsInt(); - Integer lry = hcoords.get("lry").getAsInt(); - String w = Integer.toString(lrx - ulx); - String h = Integer.toString(lry - uly); - String params = ulx + "," + uly + "," + w + "," + h; - AnnotationGenerator annot = createSearchResultAnnotation(params, text, pageId, uuid); - searchResult.addResource(annot); + searchResult.addResource(getAnnotation(highlight, pageId, uuid)); } } } @@ -185,8 +194,36 @@ public class SearchService extends AbstractResourceService { return utils.asJson(searchResult.getResource()); } - private String getCanvasId(String altoId) { - String[] identArr = altoId.split("\\."); + /** + * Returns the annotation generator for the highlight. + * @param highlight highlight element from solor response + * @param pageId page id from solr response + * @param uuid dspace item uuid + * @return generator for a single annotation + */ + private AnnotationGenerator getAnnotation(JsonElement highlight, String pageId, UUID uuid) { + JsonObject hcoords = highlight.getAsJsonObject(); + String text = (hcoords.get("text").getAsString()); + Integer ulx = hcoords.get("ulx").getAsInt(); + Integer uly = hcoords.get("uly").getAsInt(); + Integer lrx = hcoords.get("lrx").getAsInt(); + Integer lry = hcoords.get("lry").getAsInt(); + String w = Integer.toString(lrx - ulx); + String h = Integer.toString(lry - uly); + String params = ulx + "," + uly + "," + w + "," + h; + return createSearchResultAnnotation(params, text, pageId, uuid); + } + + /** + * Returns position of canvas by extracting from the pages id element. + * @param element the pages element + * @return canvas id + */ + private String getCanvasId(JsonElement element) { + JsonArray pages = element.getAsJsonArray(); + JsonObject page = pages.get(0).getAsJsonObject(); + String[] identArr = page.get("id").getAsString().split("\\."); + // the canvas id. return "c" + identArr[1]; } From 8d8e2172d0f3cb6ca56bdf12368f1e9f6dcd53b2 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 30 Mar 2021 08:05:07 -0700 Subject: [PATCH 0062/1254] Added word_highlighing solr core. --- .../conf/lang/contractions_ca.txt | 8 + .../conf/lang/contractions_fr.txt | 15 + .../conf/lang/contractions_ga.txt | 5 + .../conf/lang/contractions_it.txt | 23 + .../conf/lang/hyphenations_ga.txt | 5 + .../conf/lang/stemdict_nl.txt | 6 + .../conf/lang/stoptags_ja.txt | 420 +++++ .../conf/lang/stopwords_ar.txt | 125 ++ .../conf/lang/stopwords_bg.txt | 193 ++ .../conf/lang/stopwords_ca.txt | 220 +++ .../conf/lang/stopwords_cz.txt | 172 ++ .../conf/lang/stopwords_da.txt | 110 ++ .../conf/lang/stopwords_de.txt | 294 +++ .../conf/lang/stopwords_el.txt | 78 + .../conf/lang/stopwords_en.txt | 54 + .../conf/lang/stopwords_es.txt | 356 ++++ .../conf/lang/stopwords_et.txt | 1603 +++++++++++++++++ .../conf/lang/stopwords_eu.txt | 99 + .../conf/lang/stopwords_fa.txt | 313 ++++ .../conf/lang/stopwords_fi.txt | 97 + .../conf/lang/stopwords_fr.txt | 186 ++ .../conf/lang/stopwords_ga.txt | 110 ++ .../conf/lang/stopwords_gl.txt | 161 ++ .../conf/lang/stopwords_hi.txt | 235 +++ .../conf/lang/stopwords_hu.txt | 211 +++ .../conf/lang/stopwords_hy.txt | 46 + .../conf/lang/stopwords_id.txt | 359 ++++ .../conf/lang/stopwords_it.txt | 303 ++++ .../conf/lang/stopwords_ja.txt | 127 ++ .../conf/lang/stopwords_lv.txt | 172 ++ .../conf/lang/stopwords_nl.txt | 119 ++ .../conf/lang/stopwords_no.txt | 194 ++ .../conf/lang/stopwords_pt.txt | 253 +++ .../conf/lang/stopwords_ro.txt | 233 +++ .../conf/lang/stopwords_ru.txt | 243 +++ .../conf/lang/stopwords_sv.txt | 133 ++ .../conf/lang/stopwords_th.txt | 119 ++ .../conf/lang/stopwords_tr.txt | 212 +++ .../conf/lang/userdict_ja.txt | 29 + .../word_highlighting/conf/managed-schema | 543 ++++++ .../solr/word_highlighting/conf/protwords.txt | 21 + .../word_highlighting/conf/solrconfig.xml | 1313 ++++++++++++++ .../solr/word_highlighting/conf/stopwords.txt | 14 + .../solr/word_highlighting/conf/synonyms.txt | 29 + dspace/solr/word_highlighting/core.properties | 3 + 45 files changed, 9564 insertions(+) create mode 100644 dspace/solr/word_highlighting/conf/lang/contractions_ca.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/contractions_fr.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/contractions_ga.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/contractions_it.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/hyphenations_ga.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stemdict_nl.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stoptags_ja.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ar.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_bg.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ca.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_cz.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_da.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_de.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_el.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_en.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_es.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_et.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_eu.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_fa.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_fi.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_fr.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ga.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_gl.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_hi.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_hu.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_hy.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_id.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_it.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ja.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_lv.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_nl.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_no.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_pt.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ro.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ru.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_sv.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_th.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_tr.txt create mode 100644 dspace/solr/word_highlighting/conf/lang/userdict_ja.txt create mode 100644 dspace/solr/word_highlighting/conf/managed-schema create mode 100644 dspace/solr/word_highlighting/conf/protwords.txt create mode 100644 dspace/solr/word_highlighting/conf/solrconfig.xml create mode 100644 dspace/solr/word_highlighting/conf/stopwords.txt create mode 100644 dspace/solr/word_highlighting/conf/synonyms.txt create mode 100644 dspace/solr/word_highlighting/core.properties diff --git a/dspace/solr/word_highlighting/conf/lang/contractions_ca.txt b/dspace/solr/word_highlighting/conf/lang/contractions_ca.txt new file mode 100644 index 0000000000..307a85f913 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/contractions_ca.txt @@ -0,0 +1,8 @@ +# Set of Catalan contractions for ElisionFilter +# TODO: load this as a resource from the analyzer and sync it in build.xml +d +l +m +n +s +t diff --git a/dspace/solr/word_highlighting/conf/lang/contractions_fr.txt b/dspace/solr/word_highlighting/conf/lang/contractions_fr.txt new file mode 100644 index 0000000000..f1bba51b23 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/contractions_fr.txt @@ -0,0 +1,15 @@ +# Set of French contractions for ElisionFilter +# TODO: load this as a resource from the analyzer and sync it in build.xml +l +m +t +qu +n +s +j +d +c +jusqu +quoiqu +lorsqu +puisqu diff --git a/dspace/solr/word_highlighting/conf/lang/contractions_ga.txt b/dspace/solr/word_highlighting/conf/lang/contractions_ga.txt new file mode 100644 index 0000000000..9ebe7fa349 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/contractions_ga.txt @@ -0,0 +1,5 @@ +# Set of Irish contractions for ElisionFilter +# TODO: load this as a resource from the analyzer and sync it in build.xml +d +m +b diff --git a/dspace/solr/word_highlighting/conf/lang/contractions_it.txt b/dspace/solr/word_highlighting/conf/lang/contractions_it.txt new file mode 100644 index 0000000000..cac0409537 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/contractions_it.txt @@ -0,0 +1,23 @@ +# Set of Italian contractions for ElisionFilter +# TODO: load this as a resource from the analyzer and sync it in build.xml +c +l +all +dall +dell +nell +sull +coll +pell +gl +agl +dagl +degl +negl +sugl +un +m +t +s +v +d diff --git a/dspace/solr/word_highlighting/conf/lang/hyphenations_ga.txt b/dspace/solr/word_highlighting/conf/lang/hyphenations_ga.txt new file mode 100644 index 0000000000..4d2642cc5a --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/hyphenations_ga.txt @@ -0,0 +1,5 @@ +# Set of Irish hyphenations for StopFilter +# TODO: load this as a resource from the analyzer and sync it in build.xml +h +n +t diff --git a/dspace/solr/word_highlighting/conf/lang/stemdict_nl.txt b/dspace/solr/word_highlighting/conf/lang/stemdict_nl.txt new file mode 100644 index 0000000000..441072971d --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stemdict_nl.txt @@ -0,0 +1,6 @@ +# Set of overrides for the dutch stemmer +# TODO: load this as a resource from the analyzer and sync it in build.xml +fiets fiets +bromfiets bromfiets +ei eier +kind kinder diff --git a/dspace/solr/word_highlighting/conf/lang/stoptags_ja.txt b/dspace/solr/word_highlighting/conf/lang/stoptags_ja.txt new file mode 100644 index 0000000000..71b750845e --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stoptags_ja.txt @@ -0,0 +1,420 @@ +# +# This file defines a Japanese stoptag set for JapanesePartOfSpeechStopFilter. +# +# Any token with a part-of-speech tag that exactly matches those defined in this +# file are removed from the token stream. +# +# Set your own stoptags by uncommenting the lines below. Note that comments are +# not allowed on the same line as a stoptag. See LUCENE-3745 for frequency lists, +# etc. that can be useful for building you own stoptag set. +# +# The entire possible tagset is provided below for convenience. +# +##### +# noun: unclassified nouns +#名詞 +# +# noun-common: Common nouns or nouns where the sub-classification is undefined +#名詞-一般 +# +# noun-proper: Proper nouns where the sub-classification is undefined +#名詞-固有名詞 +# +# noun-proper-misc: miscellaneous proper nouns +#名詞-固有名詞-一般 +# +# noun-proper-person: Personal names where the sub-classification is undefined +#名詞-固有名詞-人名 +# +# noun-proper-person-misc: names that cannot be divided into surname and +# given name; foreign names; names where the surname or given name is unknown. +# e.g. お市の方 +#名詞-固有名詞-人名-一般 +# +# noun-proper-person-surname: Mainly Japanese surnames. +# e.g. 山田 +#名詞-固有名詞-人名-姓 +# +# noun-proper-person-given_name: Mainly Japanese given names. +# e.g. 太郎 +#名詞-固有名詞-人名-名 +# +# noun-proper-organization: Names representing organizations. +# e.g. 通産省, NHK +#名詞-固有名詞-組織 +# +# noun-proper-place: Place names where the sub-classification is undefined +#名詞-固有名詞-地域 +# +# noun-proper-place-misc: Place names excluding countries. +# e.g. アジア, バルセロナ, 京都 +#名詞-固有名詞-地域-一般 +# +# noun-proper-place-country: Country names. +# e.g. 日本, オーストラリア +#名詞-固有名詞-地域-国 +# +# noun-pronoun: Pronouns where the sub-classification is undefined +#名詞-代名詞 +# +# noun-pronoun-misc: miscellaneous pronouns: +# e.g. それ, ここ, あいつ, あなた, あちこち, いくつ, どこか, なに, みなさん, みんな, わたくし, われわれ +#名詞-代名詞-一般 +# +# noun-pronoun-contraction: Spoken language contraction made by combining a +# pronoun and the particle 'wa'. +# e.g. ありゃ, こりゃ, こりゃあ, そりゃ, そりゃあ +#名詞-代名詞-縮約 +# +# noun-adverbial: Temporal nouns such as names of days or months that behave +# like adverbs. Nouns that represent amount or ratios and can be used adverbially, +# e.g. 金曜, 一月, 午後, 少量 +#名詞-副詞可能 +# +# noun-verbal: Nouns that take arguments with case and can appear followed by +# 'suru' and related verbs (する, できる, なさる, くださる) +# e.g. インプット, 愛着, 悪化, 悪戦苦闘, 一安心, 下取り +#名詞-サ変接続 +# +# noun-adjective-base: The base form of adjectives, words that appear before な ("na") +# e.g. 健康, 安易, 駄目, だめ +#名詞-形容動詞語幹 +# +# noun-numeric: Arabic numbers, Chinese numerals, and counters like 何 (回), 数. +# e.g. 0, 1, 2, 何, 数, 幾 +#名詞-数 +# +# noun-affix: noun affixes where the sub-classification is undefined +#名詞-非自立 +# +# noun-affix-misc: Of adnominalizers, the case-marker の ("no"), and words that +# attach to the base form of inflectional words, words that cannot be classified +# into any of the other categories below. This category includes indefinite nouns. +# e.g. あかつき, 暁, かい, 甲斐, 気, きらい, 嫌い, くせ, 癖, こと, 事, ごと, 毎, しだい, 次第, +# 順, せい, 所為, ついで, 序で, つもり, 積もり, 点, どころ, の, はず, 筈, はずみ, 弾み, +# 拍子, ふう, ふり, 振り, ほう, 方, 旨, もの, 物, 者, ゆえ, 故, ゆえん, 所以, わけ, 訳, +# わり, 割り, 割, ん-口語/, もん-口語/ +#名詞-非自立-一般 +# +# noun-affix-adverbial: noun affixes that that can behave as adverbs. +# e.g. あいだ, 間, あげく, 挙げ句, あと, 後, 余り, 以外, 以降, 以後, 以上, 以前, 一方, うえ, +# 上, うち, 内, おり, 折り, かぎり, 限り, きり, っきり, 結果, ころ, 頃, さい, 際, 最中, さなか, +# 最中, じたい, 自体, たび, 度, ため, 為, つど, 都度, とおり, 通り, とき, 時, ところ, 所, +# とたん, 途端, なか, 中, のち, 後, ばあい, 場合, 日, ぶん, 分, ほか, 他, まえ, 前, まま, +# 儘, 侭, みぎり, 矢先 +#名詞-非自立-副詞可能 +# +# noun-affix-aux: noun affixes treated as 助動詞 ("auxiliary verb") in school grammars +# with the stem よう(だ) ("you(da)"). +# e.g. よう, やう, 様 (よう) +#名詞-非自立-助動詞語幹 +# +# noun-affix-adjective-base: noun affixes that can connect to the indeclinable +# connection form な (aux "da"). +# e.g. みたい, ふう +#名詞-非自立-形容動詞語幹 +# +# noun-special: special nouns where the sub-classification is undefined. +#名詞-特殊 +# +# noun-special-aux: The そうだ ("souda") stem form that is used for reporting news, is +# treated as 助動詞 ("auxiliary verb") in school grammars, and attach to the base +# form of inflectional words. +# e.g. そう +#名詞-特殊-助動詞語幹 +# +# noun-suffix: noun suffixes where the sub-classification is undefined. +#名詞-接尾 +# +# noun-suffix-misc: Of the nouns or stem forms of other parts of speech that connect +# to ガル or タイ and can combine into compound nouns, words that cannot be classified into +# any of the other categories below. In general, this category is more inclusive than +# 接尾語 ("suffix") and is usually the last element in a compound noun. +# e.g. おき, かた, 方, 甲斐 (がい), がかり, ぎみ, 気味, ぐるみ, (~した) さ, 次第, 済 (ず) み, +# よう, (でき)っこ, 感, 観, 性, 学, 類, 面, 用 +#名詞-接尾-一般 +# +# noun-suffix-person: Suffixes that form nouns and attach to person names more often +# than other nouns. +# e.g. 君, 様, 著 +#名詞-接尾-人名 +# +# noun-suffix-place: Suffixes that form nouns and attach to place names more often +# than other nouns. +# e.g. 町, 市, 県 +#名詞-接尾-地域 +# +# noun-suffix-verbal: Of the suffixes that attach to nouns and form nouns, those that +# can appear before スル ("suru"). +# e.g. 化, 視, 分け, 入り, 落ち, 買い +#名詞-接尾-サ変接続 +# +# noun-suffix-aux: The stem form of そうだ (様態) that is used to indicate conditions, +# is treated as 助動詞 ("auxiliary verb") in school grammars, and attach to the +# conjunctive form of inflectional words. +# e.g. そう +#名詞-接尾-助動詞語幹 +# +# noun-suffix-adjective-base: Suffixes that attach to other nouns or the conjunctive +# form of inflectional words and appear before the copula だ ("da"). +# e.g. 的, げ, がち +#名詞-接尾-形容動詞語幹 +# +# noun-suffix-adverbial: Suffixes that attach to other nouns and can behave as adverbs. +# e.g. 後 (ご), 以後, 以降, 以前, 前後, 中, 末, 上, 時 (じ) +#名詞-接尾-副詞可能 +# +# noun-suffix-classifier: Suffixes that attach to numbers and form nouns. This category +# is more inclusive than 助数詞 ("classifier") and includes common nouns that attach +# to numbers. +# e.g. 個, つ, 本, 冊, パーセント, cm, kg, カ月, か国, 区画, 時間, 時半 +#名詞-接尾-助数詞 +# +# noun-suffix-special: Special suffixes that mainly attach to inflecting words. +# e.g. (楽し) さ, (考え) 方 +#名詞-接尾-特殊 +# +# noun-suffix-conjunctive: Nouns that behave like conjunctions and join two words +# together. +# e.g. (日本) 対 (アメリカ), 対 (アメリカ), (3) 対 (5), (女優) 兼 (主婦) +#名詞-接続詞的 +# +# noun-verbal_aux: Nouns that attach to the conjunctive particle て ("te") and are +# semantically verb-like. +# e.g. ごらん, ご覧, 御覧, 頂戴 +#名詞-動詞非自立的 +# +# noun-quotation: text that cannot be segmented into words, proverbs, Chinese poetry, +# dialects, English, etc. Currently, the only entry for 名詞 引用文字列 ("noun quotation") +# is いわく ("iwaku"). +#名詞-引用文字列 +# +# noun-nai_adjective: Words that appear before the auxiliary verb ない ("nai") and +# behave like an adjective. +# e.g. 申し訳, 仕方, とんでも, 違い +#名詞-ナイ形容詞語幹 +# +##### +# prefix: unclassified prefixes +#接頭詞 +# +# prefix-nominal: Prefixes that attach to nouns (including adjective stem forms) +# excluding numerical expressions. +# e.g. お (水), 某 (氏), 同 (社), 故 (~氏), 高 (品質), お (見事), ご (立派) +#接頭詞-名詞接続 +# +# prefix-verbal: Prefixes that attach to the imperative form of a verb or a verb +# in conjunctive form followed by なる/なさる/くださる. +# e.g. お (読みなさい), お (座り) +#接頭詞-動詞接続 +# +# prefix-adjectival: Prefixes that attach to adjectives. +# e.g. お (寒いですねえ), バカ (でかい) +#接頭詞-形容詞接続 +# +# prefix-numerical: Prefixes that attach to numerical expressions. +# e.g. 約, およそ, 毎時 +#接頭詞-数接続 +# +##### +# verb: unclassified verbs +#動詞 +# +# verb-main: +#動詞-自立 +# +# verb-auxiliary: +#動詞-非自立 +# +# verb-suffix: +#動詞-接尾 +# +##### +# adjective: unclassified adjectives +#形容詞 +# +# adjective-main: +#形容詞-自立 +# +# adjective-auxiliary: +#形容詞-非自立 +# +# adjective-suffix: +#形容詞-接尾 +# +##### +# adverb: unclassified adverbs +#副詞 +# +# adverb-misc: Words that can be segmented into one unit and where adnominal +# modification is not possible. +# e.g. あいかわらず, 多分 +#副詞-一般 +# +# adverb-particle_conjunction: Adverbs that can be followed by の, は, に, +# な, する, だ, etc. +# e.g. こんなに, そんなに, あんなに, なにか, なんでも +#副詞-助詞類接続 +# +##### +# adnominal: Words that only have noun-modifying forms. +# e.g. この, その, あの, どの, いわゆる, なんらかの, 何らかの, いろんな, こういう, そういう, ああいう, +# どういう, こんな, そんな, あんな, どんな, 大きな, 小さな, おかしな, ほんの, たいした, +# 「(, も) さる (ことながら)」, 微々たる, 堂々たる, 単なる, いかなる, 我が」「同じ, 亡き +#連体詞 +# +##### +# conjunction: Conjunctions that can occur independently. +# e.g. が, けれども, そして, じゃあ, それどころか +接続詞 +# +##### +# particle: unclassified particles. +助詞 +# +# particle-case: case particles where the subclassification is undefined. +助詞-格助詞 +# +# particle-case-misc: Case particles. +# e.g. から, が, で, と, に, へ, より, を, の, にて +助詞-格助詞-一般 +# +# particle-case-quote: the "to" that appears after nouns, a person’s speech, +# quotation marks, expressions of decisions from a meeting, reasons, judgements, +# conjectures, etc. +# e.g. ( だ) と (述べた.), ( である) と (して執行猶予...) +助詞-格助詞-引用 +# +# particle-case-compound: Compounds of particles and verbs that mainly behave +# like case particles. +# e.g. という, といった, とかいう, として, とともに, と共に, でもって, にあたって, に当たって, に当って, +# にあたり, に当たり, に当り, に当たる, にあたる, において, に於いて,に於て, における, に於ける, +# にかけ, にかけて, にかんし, に関し, にかんして, に関して, にかんする, に関する, に際し, +# に際して, にしたがい, に従い, に従う, にしたがって, に従って, にたいし, に対し, にたいして, +# に対して, にたいする, に対する, について, につき, につけ, につけて, につれ, につれて, にとって, +# にとり, にまつわる, によって, に依って, に因って, により, に依り, に因り, による, に依る, に因る, +# にわたって, にわたる, をもって, を以って, を通じ, を通じて, を通して, をめぐって, をめぐり, をめぐる, +# って-口語/, ちゅう-関西弁「という」/, (何) ていう (人)-口語/, っていう-口語/, といふ, とかいふ +助詞-格助詞-連語 +# +# particle-conjunctive: +# e.g. から, からには, が, けれど, けれども, けど, し, つつ, て, で, と, ところが, どころか, とも, ども, +# ながら, なり, ので, のに, ば, ものの, や ( した), やいなや, (ころん) じゃ(いけない)-口語/, +# (行っ) ちゃ(いけない)-口語/, (言っ) たって (しかたがない)-口語/, (それがなく)ったって (平気)-口語/ +助詞-接続助詞 +# +# particle-dependency: +# e.g. こそ, さえ, しか, すら, は, も, ぞ +助詞-係助詞 +# +# particle-adverbial: +# e.g. がてら, かも, くらい, 位, ぐらい, しも, (学校) じゃ(これが流行っている)-口語/, +# (それ)じゃあ (よくない)-口語/, ずつ, (私) なぞ, など, (私) なり (に), (先生) なんか (大嫌い)-口語/, +# (私) なんぞ, (先生) なんて (大嫌い)-口語/, のみ, だけ, (私) だって-口語/, だに, +# (彼)ったら-口語/, (お茶) でも (いかが), 等 (とう), (今後) とも, ばかり, ばっか-口語/, ばっかり-口語/, +# ほど, 程, まで, 迄, (誰) も (が)([助詞-格助詞] および [助詞-係助詞] の前に位置する「も」) +助詞-副助詞 +# +# particle-interjective: particles with interjective grammatical roles. +# e.g. (松島) や +助詞-間投助詞 +# +# particle-coordinate: +# e.g. と, たり, だの, だり, とか, なり, や, やら +助詞-並立助詞 +# +# particle-final: +# e.g. かい, かしら, さ, ぜ, (だ)っけ-口語/, (とまってる) で-方言/, な, ナ, なあ-口語/, ぞ, ね, ネ, +# ねぇ-口語/, ねえ-口語/, ねん-方言/, の, のう-口語/, や, よ, ヨ, よぉ-口語/, わ, わい-口語/ +助詞-終助詞 +# +# particle-adverbial/conjunctive/final: The particle "ka" when unknown whether it is +# adverbial, conjunctive, or sentence final. For example: +# (a) 「A か B か」. Ex:「(国内で運用する) か,(海外で運用する) か (.)」 +# (b) Inside an adverb phrase. Ex:「(幸いという) か (, 死者はいなかった.)」 +# 「(祈りが届いたせい) か (, 試験に合格した.)」 +# (c) 「かのように」. Ex:「(何もなかった) か (のように振る舞った.)」 +# e.g. か +助詞-副助詞/並立助詞/終助詞 +# +# particle-adnominalizer: The "no" that attaches to nouns and modifies +# non-inflectional words. +助詞-連体化 +# +# particle-adnominalizer: The "ni" and "to" that appear following nouns and adverbs +# that are giongo, giseigo, or gitaigo. +# e.g. に, と +助詞-副詞化 +# +# particle-special: A particle that does not fit into one of the above classifications. +# This includes particles that are used in Tanka, Haiku, and other poetry. +# e.g. かな, けむ, ( しただろう) に, (あんた) にゃ(わからん), (俺) ん (家) +助詞-特殊 +# +##### +# auxiliary-verb: +助動詞 +# +##### +# interjection: Greetings and other exclamations. +# e.g. おはよう, おはようございます, こんにちは, こんばんは, ありがとう, どうもありがとう, ありがとうございます, +# いただきます, ごちそうさま, さよなら, さようなら, はい, いいえ, ごめん, ごめんなさい +#感動詞 +# +##### +# symbol: unclassified Symbols. +記号 +# +# symbol-misc: A general symbol not in one of the categories below. +# e.g. [○◎@$〒→+] +記号-一般 +# +# symbol-comma: Commas +# e.g. [,、] +記号-読点 +# +# symbol-period: Periods and full stops. +# e.g. [..。] +記号-句点 +# +# symbol-space: Full-width whitespace. +記号-空白 +# +# symbol-open_bracket: +# e.g. [({‘“『【] +記号-括弧開 +# +# symbol-close_bracket: +# e.g. [)}’”』」】] +記号-括弧閉 +# +# symbol-alphabetic: +#記号-アルファベット +# +##### +# other: unclassified other +#その他 +# +# other-interjection: Words that are hard to classify as noun-suffixes or +# sentence-final particles. +# e.g. (だ)ァ +その他-間投 +# +##### +# filler: Aizuchi that occurs during a conversation or sounds inserted as filler. +# e.g. あの, うんと, えと +フィラー +# +##### +# non-verbal: non-verbal sound. +非言語音 +# +##### +# fragment: +#語断片 +# +##### +# unknown: unknown part of speech. +#未知語 +# +##### End of file diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ar.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ar.txt new file mode 100644 index 0000000000..046829db6a --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_ar.txt @@ -0,0 +1,125 @@ +# This file was created by Jacques Savoy and is distributed under the BSD license. +# See http://members.unine.ch/jacques.savoy/clef/index.html. +# Also see http://www.opensource.org/licenses/bsd-license.html +# Cleaned on October 11, 2009 (not normalized, so use before normalization) +# This means that when modifying this list, you might need to add some +# redundant entries, for example containing forms with both أ and ا +من +ومن +منها +منه +في +وفي +فيها +فيه +و +ف +ثم +او +أو +ب +بها +به +ا +أ +اى +اي +أي +أى +لا +ولا +الا +ألا +إلا +لكن +ما +وما +كما +فما +عن +مع +اذا +إذا +ان +أن +إن +انها +أنها +إنها +انه +أنه +إنه +بان +بأن +فان +فأن +وان +وأن +وإن +التى +التي +الذى +الذي +الذين +الى +الي +إلى +إلي +على +عليها +عليه +اما +أما +إما +ايضا +أيضا +كل +وكل +لم +ولم +لن +ولن +هى +هي +هو +وهى +وهي +وهو +فهى +فهي +فهو +انت +أنت +لك +لها +له +هذه +هذا +تلك +ذلك +هناك +كانت +كان +يكون +تكون +وكانت +وكان +غير +بعض +قد +نحو +بين +بينما +منذ +ضمن +حيث +الان +الآن +خلال +بعد +قبل +حتى +عند +عندما +لدى +جميع diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_bg.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_bg.txt new file mode 100644 index 0000000000..1ae4ba2ae3 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_bg.txt @@ -0,0 +1,193 @@ +# This file was created by Jacques Savoy and is distributed under the BSD license. +# See http://members.unine.ch/jacques.savoy/clef/index.html. +# Also see http://www.opensource.org/licenses/bsd-license.html +а +аз +ако +ала +бе +без +беше +би +бил +била +били +било +близо +бъдат +бъде +бяха +в +вас +ваш +ваша +вероятно +вече +взема +ви +вие +винаги +все +всеки +всички +всичко +всяка +във +въпреки +върху +г +ги +главно +го +д +да +дали +до +докато +докога +дори +досега +доста +е +едва +един +ето +за +зад +заедно +заради +засега +затова +защо +защото +и +из +или +им +има +имат +иска +й +каза +как +каква +какво +както +какъв +като +кога +когато +което +които +кой +който +колко +която +къде +където +към +ли +м +ме +между +мен +ми +мнозина +мога +могат +може +моля +момента +му +н +на +над +назад +най +направи +напред +например +нас +не +него +нея +ни +ние +никой +нито +но +някои +някой +няма +обаче +около +освен +особено +от +отгоре +отново +още +пак +по +повече +повечето +под +поне +поради +после +почти +прави +пред +преди +през +при +пък +първо +с +са +само +се +сега +си +скоро +след +сме +според +сред +срещу +сте +съм +със +също +т +тази +така +такива +такъв +там +твой +те +тези +ти +тн +то +това +тогава +този +той +толкова +точно +трябва +тук +тъй +тя +тях +у +харесва +ч +че +често +чрез +ще +щом +я diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ca.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ca.txt new file mode 100644 index 0000000000..3da65deafe --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_ca.txt @@ -0,0 +1,220 @@ +# Catalan stopwords from http://github.com/vcl/cue.language (Apache 2 Licensed) +a +abans +ací +ah +així +això +al +als +aleshores +algun +alguna +algunes +alguns +alhora +allà +allí +allò +altra +altre +altres +amb +ambdós +ambdues +apa +aquell +aquella +aquelles +aquells +aquest +aquesta +aquestes +aquests +aquí +baix +cada +cadascú +cadascuna +cadascunes +cadascuns +com +contra +d'un +d'una +d'unes +d'uns +dalt +de +del +dels +des +després +dins +dintre +donat +doncs +durant +e +eh +el +els +em +en +encara +ens +entre +érem +eren +éreu +es +és +esta +està +estàvem +estaven +estàveu +esteu +et +etc +ets +fins +fora +gairebé +ha +han +has +havia +he +hem +heu +hi +ho +i +igual +iguals +ja +l'hi +la +les +li +li'n +llavors +m'he +ma +mal +malgrat +mateix +mateixa +mateixes +mateixos +me +mentre +més +meu +meus +meva +meves +molt +molta +moltes +molts +mon +mons +n'he +n'hi +ne +ni +no +nogensmenys +només +nosaltres +nostra +nostre +nostres +o +oh +oi +on +pas +pel +pels +per +però +perquè +poc +poca +pocs +poques +potser +propi +qual +quals +quan +quant +que +què +quelcom +qui +quin +quina +quines +quins +s'ha +s'han +sa +semblant +semblants +ses +seu +seus +seva +seva +seves +si +sobre +sobretot +sóc +solament +sols +son +són +sons +sota +sou +t'ha +t'han +t'he +ta +tal +també +tampoc +tan +tant +tanta +tantes +teu +teus +teva +teves +ton +tons +tot +tota +totes +tots +un +una +unes +uns +us +va +vaig +vam +van +vas +veu +vosaltres +vostra +vostre +vostres diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_cz.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_cz.txt new file mode 100644 index 0000000000..53c6097dac --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_cz.txt @@ -0,0 +1,172 @@ +a +s +k +o +i +u +v +z +dnes +cz +tímto +budeš +budem +byli +jseš +můj +svým +ta +tomto +tohle +tuto +tyto +jej +zda +proč +máte +tato +kam +tohoto +kdo +kteří +mi +nám +tom +tomuto +mít +nic +proto +kterou +byla +toho +protože +asi +ho +naši +napište +re +což +tím +takže +svých +její +svými +jste +aj +tu +tedy +teto +bylo +kde +ke +pravé +ji +nad +nejsou +či +pod +téma +mezi +přes +ty +pak +vám +ani +když +však +neg +jsem +tento +článku +články +aby +jsme +před +pta +jejich +byl +ještě +až +bez +také +pouze +první +vaše +která +nás +nový +tipy +pokud +může +strana +jeho +své +jiné +zprávy +nové +není +vás +jen +podle +zde +už +být +více +bude +již +než +který +by +které +co +nebo +ten +tak +má +při +od +po +jsou +jak +další +ale +si +se +ve +to +jako +za +zpět +ze +do +pro +je +na +atd +atp +jakmile +přičemž +já +on +ona +ono +oni +ony +my +vy +jí +ji +mě +mne +jemu +tomu +těm +těmu +němu +němuž +jehož +jíž +jelikož +jež +jakož +načež diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_da.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_da.txt new file mode 100644 index 0000000000..42e6145b98 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_da.txt @@ -0,0 +1,110 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/danish/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | A Danish stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + + | This is a ranked list (commonest to rarest) of stopwords derived from + | a large text sample. + + +og | and +i | in +jeg | I +det | that (dem. pronoun)/it (pers. pronoun) +at | that (in front of a sentence)/to (with infinitive) +en | a/an +den | it (pers. pronoun)/that (dem. pronoun) +til | to/at/for/until/against/by/of/into, more +er | present tense of "to be" +som | who, as +på | on/upon/in/on/at/to/after/of/with/for, on +de | they +med | with/by/in, along +han | he +af | of/by/from/off/for/in/with/on, off +for | at/for/to/from/by/of/ago, in front/before, because +ikke | not +der | who/which, there/those +var | past tense of "to be" +mig | me/myself +sig | oneself/himself/herself/itself/themselves +men | but +et | a/an/one, one (number), someone/somebody/one +har | present tense of "to have" +om | round/about/for/in/a, about/around/down, if +vi | we +min | my +havde | past tense of "to have" +ham | him +hun | she +nu | now +over | over/above/across/by/beyond/past/on/about, over/past +da | then, when/as/since +fra | from/off/since, off, since +du | you +ud | out +sin | his/her/its/one's +dem | them +os | us/ourselves +op | up +man | you/one +hans | his +hvor | where +eller | or +hvad | what +skal | must/shall etc. +selv | myself/youself/herself/ourselves etc., even +her | here +alle | all/everyone/everybody etc. +vil | will (verb) +blev | past tense of "to stay/to remain/to get/to become" +kunne | could +ind | in +når | when +være | present tense of "to be" +dog | however/yet/after all +noget | something +ville | would +jo | you know/you see (adv), yes +deres | their/theirs +efter | after/behind/according to/for/by/from, later/afterwards +ned | down +skulle | should +denne | this +end | than +dette | this +mit | my/mine +også | also +under | under/beneath/below/during, below/underneath +have | have +dig | you +anden | other +hende | her +mine | my +alt | everything +meget | much/very, plenty of +sit | his, her, its, one's +sine | his, her, its, one's +vor | our +mod | against +disse | these +hvis | if +din | your/yours +nogle | some +hos | by/at +blive | be/become +mange | many +ad | by/through +bliver | present tense of "to be/to become" +hendes | her/hers +været | be +thi | for (conj) +jer | you +sådan | such, like this/like that diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_de.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_de.txt new file mode 100644 index 0000000000..86525e7ae0 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_de.txt @@ -0,0 +1,294 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/german/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | A German stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + + | The number of forms in this list is reduced significantly by passing it + | through the German stemmer. + + +aber | but + +alle | all +allem +allen +aller +alles + +als | than, as +also | so +am | an + dem +an | at + +ander | other +andere +anderem +anderen +anderer +anderes +anderm +andern +anderr +anders + +auch | also +auf | on +aus | out of +bei | by +bin | am +bis | until +bist | art +da | there +damit | with it +dann | then + +der | the +den +des +dem +die +das + +daß | that + +derselbe | the same +derselben +denselben +desselben +demselben +dieselbe +dieselben +dasselbe + +dazu | to that + +dein | thy +deine +deinem +deinen +deiner +deines + +denn | because + +derer | of those +dessen | of him + +dich | thee +dir | to thee +du | thou + +dies | this +diese +diesem +diesen +dieser +dieses + + +doch | (several meanings) +dort | (over) there + + +durch | through + +ein | a +eine +einem +einen +einer +eines + +einig | some +einige +einigem +einigen +einiger +einiges + +einmal | once + +er | he +ihn | him +ihm | to him + +es | it +etwas | something + +euer | your +eure +eurem +euren +eurer +eures + +für | for +gegen | towards +gewesen | p.p. of sein +hab | have +habe | have +haben | have +hat | has +hatte | had +hatten | had +hier | here +hin | there +hinter | behind + +ich | I +mich | me +mir | to me + + +ihr | you, to her +ihre +ihrem +ihren +ihrer +ihres +euch | to you + +im | in + dem +in | in +indem | while +ins | in + das +ist | is + +jede | each, every +jedem +jeden +jeder +jedes + +jene | that +jenem +jenen +jener +jenes + +jetzt | now +kann | can + +kein | no +keine +keinem +keinen +keiner +keines + +können | can +könnte | could +machen | do +man | one + +manche | some, many a +manchem +manchen +mancher +manches + +mein | my +meine +meinem +meinen +meiner +meines + +mit | with +muss | must +musste | had to +nach | to(wards) +nicht | not +nichts | nothing +noch | still, yet +nun | now +nur | only +ob | whether +oder | or +ohne | without +sehr | very + +sein | his +seine +seinem +seinen +seiner +seines + +selbst | self +sich | herself + +sie | they, she +ihnen | to them + +sind | are +so | so + +solche | such +solchem +solchen +solcher +solches + +soll | shall +sollte | should +sondern | but +sonst | else +über | over +um | about, around +und | and + +uns | us +unse +unsem +unsen +unser +unses + +unter | under +viel | much +vom | von + dem +von | from +vor | before +während | while +war | was +waren | were +warst | wast +was | what +weg | away, off +weil | because +weiter | further + +welche | which +welchem +welchen +welcher +welches + +wenn | when +werde | will +werden | will +wie | how +wieder | again +will | want +wir | we +wird | will +wirst | willst +wo | where +wollen | want +wollte | wanted +würde | would +würden | would +zu | to +zum | zu + dem +zur | zu + der +zwar | indeed +zwischen | between + diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_el.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_el.txt new file mode 100644 index 0000000000..232681f5bd --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_el.txt @@ -0,0 +1,78 @@ +# Lucene Greek Stopwords list +# Note: by default this file is used after GreekLowerCaseFilter, +# so when modifying this file use 'σ' instead of 'ς' +ο +η +το +οι +τα +του +τησ +των +τον +την +και +κι +κ +ειμαι +εισαι +ειναι +ειμαστε +ειστε +στο +στον +στη +στην +μα +αλλα +απο +για +προσ +με +σε +ωσ +παρα +αντι +κατα +μετα +θα +να +δε +δεν +μη +μην +επι +ενω +εαν +αν +τοτε +που +πωσ +ποιοσ +ποια +ποιο +ποιοι +ποιεσ +ποιων +ποιουσ +αυτοσ +αυτη +αυτο +αυτοι +αυτων +αυτουσ +αυτεσ +αυτα +εκεινοσ +εκεινη +εκεινο +εκεινοι +εκεινεσ +εκεινα +εκεινων +εκεινουσ +οπωσ +ομωσ +ισωσ +οσο +οτι diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_en.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_en.txt new file mode 100644 index 0000000000..2c164c0b2a --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_en.txt @@ -0,0 +1,54 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# a couple of test stopwords to test that the words are really being +# configured from this file: +stopworda +stopwordb + +# Standard english stop words taken from Lucene's StopAnalyzer +a +an +and +are +as +at +be +but +by +for +if +in +into +is +it +no +not +of +on +or +such +that +the +their +then +there +these +they +this +to +was +will +with diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_es.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_es.txt new file mode 100644 index 0000000000..487d78c8d5 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_es.txt @@ -0,0 +1,356 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/spanish/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | A Spanish stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + + + | The following is a ranked list (commonest to rarest) of stopwords + | deriving from a large sample of text. + + | Extra words have been added at the end. + +de | from, of +la | the, her +que | who, that +el | the +en | in +y | and +a | to +los | the, them +del | de + el +se | himself, from him etc +las | the, them +por | for, by, etc +un | a +para | for +con | with +no | no +una | a +su | his, her +al | a + el + | es from SER +lo | him +como | how +más | more +pero | pero +sus | su plural +le | to him, her +ya | already +o | or + | fue from SER +este | this + | ha from HABER +sí | himself etc +porque | because +esta | this + | son from SER +entre | between + | está from ESTAR +cuando | when +muy | very +sin | without +sobre | on + | ser from SER + | tiene from TENER +también | also +me | me +hasta | until +hay | there is/are +donde | where + | han from HABER +quien | whom, that + | están from ESTAR + | estado from ESTAR +desde | from +todo | all +nos | us +durante | during + | estados from ESTAR +todos | all +uno | a +les | to them +ni | nor +contra | against +otros | other + | fueron from SER +ese | that +eso | that + | había from HABER +ante | before +ellos | they +e | and (variant of y) +esto | this +mí | me +antes | before +algunos | some +qué | what? +unos | a +yo | I +otro | other +otras | other +otra | other +él | he +tanto | so much, many +esa | that +estos | these +mucho | much, many +quienes | who +nada | nothing +muchos | many +cual | who + | sea from SER +poco | few +ella | she +estar | to be + | haber from HABER +estas | these + | estaba from ESTAR + | estamos from ESTAR +algunas | some +algo | something +nosotros | we + + | other forms + +mi | me +mis | mi plural +tú | thou +te | thee +ti | thee +tu | thy +tus | tu plural +ellas | they +nosotras | we +vosotros | you +vosotras | you +os | you +mío | mine +mía | +míos | +mías | +tuyo | thine +tuya | +tuyos | +tuyas | +suyo | his, hers, theirs +suya | +suyos | +suyas | +nuestro | ours +nuestra | +nuestros | +nuestras | +vuestro | yours +vuestra | +vuestros | +vuestras | +esos | those +esas | those + + | forms of estar, to be (not including the infinitive): +estoy +estás +está +estamos +estáis +están +esté +estés +estemos +estéis +estén +estaré +estarás +estará +estaremos +estaréis +estarán +estaría +estarías +estaríamos +estaríais +estarían +estaba +estabas +estábamos +estabais +estaban +estuve +estuviste +estuvo +estuvimos +estuvisteis +estuvieron +estuviera +estuvieras +estuviéramos +estuvierais +estuvieran +estuviese +estuvieses +estuviésemos +estuvieseis +estuviesen +estando +estado +estada +estados +estadas +estad + + | forms of haber, to have (not including the infinitive): +he +has +ha +hemos +habéis +han +haya +hayas +hayamos +hayáis +hayan +habré +habrás +habrá +habremos +habréis +habrán +habría +habrías +habríamos +habríais +habrían +había +habías +habíamos +habíais +habían +hube +hubiste +hubo +hubimos +hubisteis +hubieron +hubiera +hubieras +hubiéramos +hubierais +hubieran +hubiese +hubieses +hubiésemos +hubieseis +hubiesen +habiendo +habido +habida +habidos +habidas + + | forms of ser, to be (not including the infinitive): +soy +eres +es +somos +sois +son +sea +seas +seamos +seáis +sean +seré +serás +será +seremos +seréis +serán +sería +serías +seríamos +seríais +serían +era +eras +éramos +erais +eran +fui +fuiste +fue +fuimos +fuisteis +fueron +fuera +fueras +fuéramos +fuerais +fueran +fuese +fueses +fuésemos +fueseis +fuesen +siendo +sido + | sed also means 'thirst' + + | forms of tener, to have (not including the infinitive): +tengo +tienes +tiene +tenemos +tenéis +tienen +tenga +tengas +tengamos +tengáis +tengan +tendré +tendrás +tendrá +tendremos +tendréis +tendrán +tendría +tendrías +tendríamos +tendríais +tendrían +tenía +tenías +teníamos +teníais +tenían +tuve +tuviste +tuvo +tuvimos +tuvisteis +tuvieron +tuviera +tuvieras +tuviéramos +tuvierais +tuvieran +tuviese +tuvieses +tuviésemos +tuvieseis +tuviesen +teniendo +tenido +tenida +tenidos +tenidas +tened + diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_et.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_et.txt new file mode 100644 index 0000000000..1b06a134b9 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_et.txt @@ -0,0 +1,1603 @@ +# Estonian stopwords list +all +alla +allapoole +allpool +alt +altpoolt +eel +eespool +enne +hommikupoole +hoolimata +ilma +kaudu +keset +kesk +kohe +koos +kuhupoole +kuni +kuspool +kustpoolt +kõige +käsikäes +lappi +ligi +läbi +mööda +paitsi +peale +pealepoole +pealpool +pealt +pealtpoolt +piki +pikku +piku +pikuti +põiki +pärast +päri +risti +sealpool +sealtpoolt +seespool +seltsis +siiapoole +siinpool +siitpoolt +sinnapoole +sissepoole +taga +tagantpoolt +tagapidi +tagapool +taha +tahapoole +teispool +teispoole +tänu +tükkis +vaatamata +vastu +väljapoole +väljaspool +väljastpoolt +õhtupoole +ühes +ühestükis +ühestükkis +ülalpool +ülaltpoolt +üle +ülespoole +ülevalpool +ülevaltpoolt +ümber +ümbert +aegu +aegus +alguks +algul +algule +algult +alguni +all +alla +alt +alul +alutsi +arvel +asemel +asemele +eel +eeli +ees +eesotsas +eest +eestotsast +esitsi +ette +etteotsa +haaval +heaks +hoolimata +hulgas +hulgast +hulka +jalgu +jalus +jalust +jaoks +jooksul +juurde +juures +juurest +jälil +jälile +järel +järele +järelt +järgi +kaasas +kallal +kallale +kallalt +kamul +kannul +kannule +kannult +kaudu +kaupa +keskel +keskele +keskelt +keskis +keskpaiku +kestel +kestes +kilda +killas +killast +kimpu +kimpus +kiuste +kohal +kohale +kohalt +kohaselt +kohe +kohta +koos +korral +kukil +kukile +kukilt +kulul +kõrva +kõrval +kõrvale +kõrvalt +kõrvas +kõrvast +käekõrval +käekõrvale +käekõrvalt +käes +käest +kätte +külge +küljes +küljest +küüsi +küüsis +küüsist +ligi +ligidal +ligidale +ligidalt +aegu +aegus +alguks +algul +algule +algult +alguni +all +alla +alt +alul +alutsi +arvel +asemel +asemele +eel +eeli +ees +eesotsas +eest +eestotsast +esitsi +ette +etteotsa +haaval +heaks +hoolimata +hulgas +hulgast +hulka +jalgu +jalus +jalust +jaoks +jooksul +juurde +juures +juurest +jälil +jälile +järel +järele +järelt +järgi +kaasas +kallal +kallale +kallalt +kamul +kannul +kannule +kannult +kaudu +kaupa +keskel +keskele +keskelt +keskis +keskpaiku +kestel +kestes +kilda +killas +killast +kimpu +kimpus +kiuste +kohal +kohale +kohalt +kohaselt +kohe +kohta +koos +korral +kukil +kukile +kukilt +kulul +kõrva +kõrval +kõrvale +kõrvalt +kõrvas +kõrvast +käekõrval +käekõrvale +käekõrvalt +käes +käest +kätte +külge +küljes +küljest +küüsi +küüsis +küüsist +ligi +ligidal +ligidale +ligidalt +lool +läbi +lähedal +lähedale +lähedalt +man +mant +manu +meelest +mööda +nahas +nahka +nahkas +najal +najale +najalt +nõjal +nõjale +otsa +otsas +otsast +paigale +paigu +paiku +peal +peale +pealt +perra +perrä +pidi +pihta +piki +pikku +pool +poole +poolest +poolt +puhul +puksiiris +pähe +päralt +päras +pärast +päri +ringi +ringis +risust +saadetusel +saadik +saatel +saati +seas +seast +sees +seest +sekka +seljataga +seltsi +seltsis +seltsist +sisse +slepis +suhtes +šlepis +taga +tagant +tagantotsast +tagaotsas +tagaselja +tagasi +tagast +tagutsi +taha +tahaotsa +takka +tarvis +tasa +tuuri +tuuris +tõttu +tükkis +uhal +vaatamata +vahel +vahele +vahelt +vahepeal +vahepeale +vahepealt +vahetsi +varal +varale +varul +vastas +vastast +vastu +veerde +veeres +viisi +võidu +võrd +võrdki +võrra +võrragi +väel +väele +vältel +väärt +väärtki +äärde +ääre +ääres +äärest +ühes +üle +ümber +ümbert +a +abil +aina +ainult +alalt +alates +alati +alles +b +c +d +e +eales +ealeski +edasi +edaspidi +eelkõige +eemal +ei +eks +end +enda +enese +ennem +esialgu +f +g +h +hoopis +i +iganes +igatahes +igati +iial +iialgi +ikka +ikkagi +ilmaski +iseenda +iseenese +iseenesest +isegi +j +jah +ju +juba +juhul +just +järelikult +k +ka +kah +kas +kasvõi +keda +kestahes +kogu +koguni +kohati +kokku +kuhu +kuhugi +kuidagi +kuidas +kunagi +kus +kusagil +kusjuures +kuskil +kust +kõigepealt +küll +l +liiga +lisaks +m +miks +mil +millal +millalgi +mispärast +mistahes +mistõttu +mitte +muide +muidu +muidugi +muist +mujal +mujale +mujalt +mõlemad +mõnda +mõne +mõnikord +n +nii +niikaua +niimoodi +niipaljuke +niisama +niisiis +niivõrd +nõnda +nüüd +o +omaette +omakorda +omavahel +ometi +p +palju +paljuke +palju-palju +peaaegu +peagi +peamiselt +pigem +pisut +praegu +päris +r +rohkem +s +samas +samuti +seal +sealt +sedakorda +sedapuhku +seega +seejuures +seejärel +seekord +seepärast +seetõttu +sellepärast +seni +sestap +siia +siiani +siin +siinkohal +siis +siiski +siit +sinna +suht +š +z +ž +t +teel +teineteise +tõesti +täiesti +u +umbes +v +w +veel +veelgi +vist +võibolla +võib-olla +väga +vähemalt +välja +väljas +väljast +õ +ä +ära +ö +ü +ühtlasi +üksi +ükskõik +ülal +ülale +ülalt +üles +ülesse +üleval +ülevalt +ülimalt +üsna +x +y +aga +ega +ehk +ehkki +elik +ellik +enge +ennegu +ent +et +ja +justkui +kui +kuid +kuigi +kuivõrd +kuna +kuni +kut +mistab +muudkui +nagu +nigu +ning +olgugi +otsekui +otsenagu +selmet +sest +sestab +vaid +või +aa +adaa +adjöö +ae +ah +ahaa +ahah +ah-ah-ah +ah-haa +ahoi +ai +aidaa +aidu-raidu +aih +aijeh +aituma +aitäh +aitüma +ammuu +amps +ampsti +aptsih +ass +at +ata +at-at-at +atsih +atsihh +auh +bai-bai +bingo +braavo +brr +ee +eeh +eh +ehee +eheh +eh-eh-hee +eh-eh-ee +ehei +ehh +ehhee +einoh +ena +ennäe +ennäh +fuh +fui +fuih +haa +hah +hahaa +hah-hah-hah +halleluuja +hallo +halloo +hass +hee +heh +he-he-hee +hei +heldeke(ne) +heureka +hihii +hip-hip-hurraa +hmh +hmjah +hoh-hoh-hoo +hohoo +hoi +hollallaa +hoo +hoplaa +hopp +hops +hopsassaa +hopsti +hosianna +huh +huidii +huist +hurjah +hurjeh +hurjoh +hurjuh +hurraa +huu +hõhõh +hõi +hõissa +hõissassa +hõk +hõkk +häh +hä-hä-hää +hüvasti +ih-ah-haa +ih-ih-hii +ii-ha-ha +issake +issakene +isver +jaa-ah +ja-ah +jaah +janäe +jeeh +jeerum +jeever +jessas +jestas +juhhei +jumalaga +jumalime +jumaluke +jumalukene +jutas +kaaps +kaapsti +kaasike +kae +kalps +kalpsti +kannäe +kanäe +kappadi +kaps +kapsti +karkõmm +karkäuh +karkääks +karkääksti +karmauh +karmauhti +karnaps +karnapsti +karniuhti +karpartsaki +karpauh +karpauhti +karplauh +karplauhti +karprauh +karprauhti +karsumdi +karsumm +kartsumdi +kartsumm +karviuh +karviuhti +kaske +kassa +kauh +kauhti +keh +keksti +kepsti +khe +khm +kih +kiiks +kiiksti +kiis +kiiss +kikerii +kikerikii +kili +kilk +kilk-kõlk +kilks +kilks-kolks +kilks-kõlks +kill +killadi +killadi|-kolladi +killadi-kõlladi +killa-kolla +killa-kõlla +kill-kõll +kimps-komps +kipp +kips-kõps +kiriküüt +kirra-kõrra +kirr-kõrr +kirts +klaps +klapsti +klirdi +klirr +klonks +klops +klopsti +kluk +klu-kluu +klõks +klõksti +klõmdi +klõmm +klõmpsti +klõnks +klõnksti +klõps +klõpsti +kläu +kohva-kohva +kok +koks +koksti +kolaki +kolk +kolks +kolksti +koll +kolladi +komp +komps +kompsti +kop +kopp +koppadi +kops +kopsti +kossu +kotsu +kraa +kraak +kraaks +kraaps +kraapsti +krahh +kraks +kraksti +kraps +krapsti +krauh +krauhti +kriiks +kriiksti +kriips +kriips-kraaps +kripa-krõpa +krips-kraps +kriuh +kriuks +kriuksti +kromps +kronk +kronks +krooks +kruu +krõks +krõksti +krõpa +krõps +krõpsti +krõuh +kräu +kräuh +kräuhti +kräuks +kss +kukeleegu +kukku +kuku +kulu +kurluu +kurnäu +kuss +kussu +kõks +kõksti +kõldi +kõlks +kõlksti +kõll +kõmaki +kõmdi +kõmm +kõmps +kõpp +kõps +kõpsadi +kõpsat +kõpsti +kõrr +kõrra-kõrra +kõss +kõtt +kõõksti +kärr +kärts +kärtsti +käuks +käuksti +kääga +kääks +kääksti +köh +köki-möki +köksti +laks +laksti +lampsti +larts +lartsti +lats +latsti +leelo +legoo +lehva +liiri-lõõri +lika-lõka +likat-lõkat +limpsti +lips +lipsti +lirts +lirtsaki +lirtsti +lonksti +lops +lopsti +lorts +lortsti +luks +lups +lupsti +lurts +lurtsti +lõks +lõksti +lõmps +lõmpsti +lõnks +lõnksti +lärts +lärtsti +läts +lätsti +lörts +lörtsti +lötsti +lööps +lööpsti +marss +mats +matsti +mauh +mauhti +mh +mhh +mhmh +miau +mjaa +mkm +m-mh +mnjaa +mnjah +moens +mulks +mulksti +mull-mull +mull-mull-mull +muu +muuh +mõh +mõmm +mäh +mäts +mäu +mää +möh +möh-öh-ää +möö +müh-müh +mühüh +müks +müksti +müraki +mürr +mürts +mürtsaki +mürtsti +mütaku +müta-mäta +müta-müta +müt-müt +müt-müt-müt +müts +mütsti +mütt +naa +naah +nah +naks +naksti +nanuu +naps +napsti +nilpsti +nipsti +nirr +niuh +niuh-näuh +niuhti +noh +noksti +nolpsti +nonoh +nonoo +nonäh +noo +nooh +nooks +norr +nurr +nuuts +nõh +nõhh +nõka-nõka +nõks +nõksat-nõksat +nõks-nõks +nõksti +nõõ +nõõh +näeh +näh +nälpsti +nämm-nämm +näpsti +näts +nätsti +näu +näuh +näuhti +näuks +näuksti +nääh +nääks +nühkat-nühkat +oeh +oh +ohh +ohhh +oh-hoi +oh-hoo +ohoh +oh-oh-oo +oh-oh-hoo +ohoi +ohoo +oi +oih +oijee +oijeh +oo +ooh +oo-oh +oo-ohh +oot +ossa +ot +paa +pah +pahh +pakaa +pamm +pantsti +pardon +pardonks +parlartsti +parts +partsti +partsumdi +partsumm +pastoi +pats +patst +patsti +pau +pauh +pauhti +pele +pfui +phuh +phuuh +phäh +phähh +piiks +piip +piiri-pääri +pimm +pimm-pamm +pimm-pomm +pimm-põmm +piraki +piuks +piu-pau +plaks +plaksti +plarts +plartsti +plats +platsti +plauh +plauhh +plauhti +pliks +pliks-plaks +plinn +pliraki +plirts +plirtsti +pliu +pliuh +ploks +plotsti +plumps +plumpsti +plõks +plõksti +plõmdi +plõmm +plõnn +plärr +plärts +plärtsat +plärtsti +pläu +pläuh +plää +plörtsat +pomm +popp +pops +popsti +ports +pot +pots +potsti +pott +praks +praksti +prants +prantsaki +prantsti +prassai +prauh +prauhh +prauhti +priks +priuh +priuhh +priuh-prauh +proosit +proost +prr +prrr +prõks +prõksti +prõmdi +prõmm +prõntsti +prääk +prääks +pst +psst +ptrr +ptruu +ptüi +puh +puhh +puksti +pumm +pumps +pup-pup-pup +purts +puuh +põks +põksti +põmdi +põmm +põmmadi +põnks +põnn +põnnadi +põnt +põnts +põntsti +põraki +põrr +põrra-põrra +päh +pähh +päntsti +pää +pöörd +püh +raks +raksti +raps +rapsti +ratataa +rauh +riips +riipsti +riks +riks-raks +rips-raps +rivitult +robaki +rops +ropsaki +ropsti +ruik +räntsti +räts +röh +röhh +sah +sahh +sahkat +saps +sapsti +sauh +sauhti +servus +sihkadi-sahkadi +sihka-sahka +sihkat-sahkat +silks +silk-solk +sips +sipsti +sirr +sirr-sorr +sirts +sirtsti +siu +siuh +siuh-sauh +siuh-säuh +siuhti +siuks +siuts +skool +so +soh +solks +solksti +solpsti +soo +sooh +so-oh +soo-oh +sopp +sops +sopsti +sorr +sorts +sortsti +so-soo +soss +soss-soss +ss +sss +sst +stopp +suhkat-sahkat +sulk +sulks +sulksti +sull +sulla-sulla +sulpa-sulpa +sulps +sulpsti +sumaki +sumdi +summ +summat-summat +sups +supsaku +supsti +surts +surtsti +suss +susti +suts +sutsti +säh +sähke +särts +särtsti +säu +säuh +säuhti +taevake +taevakene +takk +tere +terekest +tibi-tibi +tikk-takk +tiks +tilk +tilks +till +tilla-talla +till-tall +tilulii +tinn +tip +tip-tap +tirr +tirtsti +tiu +tjaa +tjah +tohhoh +tohhoo +tohoh +tohoo +tok +tokk +toks +toksti +tonks +tonksti +tota +totsti +tot-tot +tprr +tpruu +trah +trahh +trallallaa +trill +trillallaa +trr +trrr +tsah +tsahh +tsilk +tsilk-tsolk +tsirr +tsiuh +tskae +tsolk +tss +tst +tsst +tsuhh +tsuk +tsumm +tsurr +tsäuh +tšao +tšš +tššš +tuk +tuks +turts +turtsti +tutki +tutkit +tutu-lutu +tutulutu +tuut +tuutu-luutu +tõks +tötsti +tümps +uh +uhh +uh-huu +uhtsa +uhtsaa +uhuh +uhuu +ui +uih +uih-aih +uijah +uijeh +uist +uit +uka +upsti +uraa +urjah +urjeh +urjoh +urjuh +urr +urraa +ust +utu +uu +uuh +vaak +vaat +vae +vaeh +vai +vat +vau +vhüüt +vidiit +viiks +vilks +vilksti +vinki-vinki +virdi +virr +viu +viudi +viuh +viuhti +voeh +voh +vohh +volks +volksti +vooh +vops +vopsti +vot +vuh +vuhti +vuih +vulks +vulksti +vull +vulpsti +vups +vupsaki +vupsaku +vupsti +vurdi +vurr +vurra-vurra +vurts +vurtsti +vutt +võe +võeh +või +võih +võrr +võts +võtt +vääks +õe +õits +õk +õkk +õrr +õss +õuh +äh +ähh +ähhähhää +äh-hää +äh-äh-hää +äiu +äiu-ää +äss +ää +ääh +äähh +öh +öhh +ök +üh +eelmine +eikeegi +eimiski +emb-kumb +enam +enim +iga +igasugune +igaüks +ise +isesugune +järgmine +keegi +kes +kumb +kumbki +kõik +meiesugune +meietaoline +midagi +mihuke +mihukene +milletaoline +milline +mina +minake +mingi +mingisugune +minusugune +minutaoline +mis +miski +miskisugune +missugune +misuke +mitmes +mitmesugune +mitu +mitu-mitu +mitu-setu +muu +mõlema +mõnesugune +mõni +mõningane +mõningas +mäherdune +määrane +naasugune +need +nemad +nendesugune +nendetaoline +nihuke +nihukene +niimitu +niisamasugune +niisugune +nisuke +nisukene +oma +omaenese +omasugune +omataoline +pool +praegune +sama +samasugune +samataoline +see +seesama +seesamane +seesamune +seesinane +seesugune +selline +sihuke +sihukene +sina +sinusugune +sinutaoline +siuke +siukene +säherdune +säärane +taoline +teiesugune +teine +teistsugune +tema +temake +temakene +temasugune +temataoline +too +toosama +toosamane +üks +üksteise +hakkama +minema +olema +pidama +saama +tegema +tulema +võima diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_eu.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_eu.txt new file mode 100644 index 0000000000..25f1db9346 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_eu.txt @@ -0,0 +1,99 @@ +# example set of basque stopwords +al +anitz +arabera +asko +baina +bat +batean +batek +bati +batzuei +batzuek +batzuetan +batzuk +bera +beraiek +berau +berauek +bere +berori +beroriek +beste +bezala +da +dago +dira +ditu +du +dute +edo +egin +ere +eta +eurak +ez +gainera +gu +gutxi +guzti +haiei +haiek +haietan +hainbeste +hala +han +handik +hango +hara +hari +hark +hartan +hau +hauei +hauek +hauetan +hemen +hemendik +hemengo +hi +hona +honek +honela +honetan +honi +hor +hori +horiei +horiek +horietan +horko +horra +horrek +horrela +horretan +horri +hortik +hura +izan +ni +noiz +nola +non +nondik +nongo +nor +nora +ze +zein +zen +zenbait +zenbat +zer +zergatik +ziren +zituen +zu +zuek +zuen +zuten diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_fa.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_fa.txt new file mode 100644 index 0000000000..723641c6da --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_fa.txt @@ -0,0 +1,313 @@ +# This file was created by Jacques Savoy and is distributed under the BSD license. +# See http://members.unine.ch/jacques.savoy/clef/index.html. +# Also see http://www.opensource.org/licenses/bsd-license.html +# Note: by default this file is used after normalization, so when adding entries +# to this file, use the arabic 'ي' instead of 'ی' +انان +نداشته +سراسر +خياه +ايشان +وي +تاكنون +بيشتري +دوم +پس +ناشي +وگو +يا +داشتند +سپس +هنگام +هرگز +پنج +نشان +امسال +ديگر +گروهي +شدند +چطور +ده +و +دو +نخستين +ولي +چرا +چه +وسط +ه +كدام +قابل +يك +رفت +هفت +همچنين +در +هزار +بله +بلي +شايد +اما +شناسي +گرفته +دهد +داشته +دانست +داشتن +خواهيم +ميليارد +وقتيكه +امد +خواهد +جز +اورده +شده +بلكه +خدمات +شدن +برخي +نبود +بسياري +جلوگيري +حق +كردند +نوعي +بعري +نكرده +نظير +نبايد +بوده +بودن +داد +اورد +هست +جايي +شود +دنبال +داده +بايد +سابق +هيچ +همان +انجا +كمتر +كجاست +گردد +كسي +تر +مردم +تان +دادن +بودند +سري +جدا +ندارند +مگر +يكديگر +دارد +دهند +بنابراين +هنگامي +سمت +جا +انچه +خود +دادند +زياد +دارند +اثر +بدون +بهترين +بيشتر +البته +به +براساس +بيرون +كرد +بعضي +گرفت +توي +اي +ميليون +او +جريان +تول +بر +مانند +برابر +باشيم +مدتي +گويند +اكنون +تا +تنها +جديد +چند +بي +نشده +كردن +كردم +گويد +كرده +كنيم +نمي +نزد +روي +قصد +فقط +بالاي +ديگران +اين +ديروز +توسط +سوم +ايم +دانند +سوي +استفاده +شما +كنار +داريم +ساخته +طور +امده +رفته +نخست +بيست +نزديك +طي +كنيد +از +انها +تمامي +داشت +يكي +طريق +اش +چيست +روب +نمايد +گفت +چندين +چيزي +تواند +ام +ايا +با +ان +ايد +ترين +اينكه +ديگري +راه +هايي +بروز +همچنان +پاعين +كس +حدود +مختلف +مقابل +چيز +گيرد +ندارد +ضد +همچون +سازي +شان +مورد +باره +مرسي +خويش +برخوردار +چون +خارج +شش +هنوز +تحت +ضمن +هستيم +گفته +فكر +بسيار +پيش +براي +روزهاي +انكه +نخواهد +بالا +كل +وقتي +كي +چنين +كه +گيري +نيست +است +كجا +كند +نيز +يابد +بندي +حتي +توانند +عقب +خواست +كنند +بين +تمام +همه +ما +باشند +مثل +شد +اري +باشد +اره +طبق +بعد +اگر +صورت +غير +جاي +بيش +ريزي +اند +زيرا +چگونه +بار +لطفا +مي +درباره +من +ديده +همين +گذاري +برداري +علت +گذاشته +هم +فوق +نه +ها +شوند +اباد +همواره +هر +اول +خواهند +چهار +نام +امروز +مان +هاي +قبل +كنم +سعي +تازه +را +هستند +زير +جلوي +عنوان +بود diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_fi.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_fi.txt new file mode 100644 index 0000000000..4372c9a055 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_fi.txt @@ -0,0 +1,97 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/finnish/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + +| forms of BE + +olla +olen +olet +on +olemme +olette +ovat +ole | negative form + +oli +olisi +olisit +olisin +olisimme +olisitte +olisivat +olit +olin +olimme +olitte +olivat +ollut +olleet + +en | negation +et +ei +emme +ette +eivät + +|Nom Gen Acc Part Iness Elat Illat Adess Ablat Allat Ess Trans +minä minun minut minua minussa minusta minuun minulla minulta minulle | I +sinä sinun sinut sinua sinussa sinusta sinuun sinulla sinulta sinulle | you +hän hänen hänet häntä hänessä hänestä häneen hänellä häneltä hänelle | he she +me meidän meidät meitä meissä meistä meihin meillä meiltä meille | we +te teidän teidät teitä teissä teistä teihin teillä teiltä teille | you +he heidän heidät heitä heissä heistä heihin heillä heiltä heille | they + +tämä tämän tätä tässä tästä tähän tallä tältä tälle tänä täksi | this +tuo tuon tuotä tuossa tuosta tuohon tuolla tuolta tuolle tuona tuoksi | that +se sen sitä siinä siitä siihen sillä siltä sille sinä siksi | it +nämä näiden näitä näissä näistä näihin näillä näiltä näille näinä näiksi | these +nuo noiden noita noissa noista noihin noilla noilta noille noina noiksi | those +ne niiden niitä niissä niistä niihin niillä niiltä niille niinä niiksi | they + +kuka kenen kenet ketä kenessä kenestä keneen kenellä keneltä kenelle kenenä keneksi| who +ketkä keiden ketkä keitä keissä keistä keihin keillä keiltä keille keinä keiksi | (pl) +mikä minkä minkä mitä missä mistä mihin millä miltä mille minä miksi | which what +mitkä | (pl) + +joka jonka jota jossa josta johon jolla jolta jolle jona joksi | who which +jotka joiden joita joissa joista joihin joilla joilta joille joina joiksi | (pl) + +| conjunctions + +että | that +ja | and +jos | if +koska | because +kuin | than +mutta | but +niin | so +sekä | and +sillä | for +tai | or +vaan | but +vai | or +vaikka | although + + +| prepositions + +kanssa | with +mukaan | according to +noin | about +poikki | across +yli | over, across + +| other + +kun | when +niin | so +nyt | now +itse | self + diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_fr.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_fr.txt new file mode 100644 index 0000000000..749abae684 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_fr.txt @@ -0,0 +1,186 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/french/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | A French stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + +au | a + le +aux | a + les +avec | with +ce | this +ces | these +dans | with +de | of +des | de + les +du | de + le +elle | she +en | `of them' etc +et | and +eux | them +il | he +je | I +la | the +le | the +leur | their +lui | him +ma | my (fem) +mais | but +me | me +même | same; as in moi-même (myself) etc +mes | me (pl) +moi | me +mon | my (masc) +ne | not +nos | our (pl) +notre | our +nous | we +on | one +ou | where +par | by +pas | not +pour | for +qu | que before vowel +que | that +qui | who +sa | his, her (fem) +se | oneself +ses | his (pl) +son | his, her (masc) +sur | on +ta | thy (fem) +te | thee +tes | thy (pl) +toi | thee +ton | thy (masc) +tu | thou +un | a +une | a +vos | your (pl) +votre | your +vous | you + + | single letter forms + +c | c' +d | d' +j | j' +l | l' +à | to, at +m | m' +n | n' +s | s' +t | t' +y | there + + | forms of être (not including the infinitive): +été +étée +étées +étés +étant +suis +es +est +sommes +êtes +sont +serai +seras +sera +serons +serez +seront +serais +serait +serions +seriez +seraient +étais +était +étions +étiez +étaient +fus +fut +fûmes +fûtes +furent +sois +soit +soyons +soyez +soient +fusse +fusses +fût +fussions +fussiez +fussent + + | forms of avoir (not including the infinitive): +ayant +eu +eue +eues +eus +ai +as +avons +avez +ont +aurai +auras +aura +aurons +aurez +auront +aurais +aurait +aurions +auriez +auraient +avais +avait +avions +aviez +avaient +eut +eûmes +eûtes +eurent +aie +aies +ait +ayons +ayez +aient +eusse +eusses +eût +eussions +eussiez +eussent + + | Later additions (from Jean-Christophe Deschamps) +ceci | this +cela | that +celà | that +cet | this +cette | this +ici | here +ils | they +les | the (pl) +leurs | their (pl) +quel | which +quels | which +quelle | which +quelles | which +sans | without +soi | oneself + diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ga.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ga.txt new file mode 100644 index 0000000000..9ff88d747e --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_ga.txt @@ -0,0 +1,110 @@ + +a +ach +ag +agus +an +aon +ar +arna +as +b' +ba +beirt +bhúr +caoga +ceathair +ceathrar +chomh +chtó +chuig +chun +cois +céad +cúig +cúigear +d' +daichead +dar +de +deich +deichniúr +den +dhá +do +don +dtí +dá +dár +dó +faoi +faoin +faoina +faoinár +fara +fiche +gach +gan +go +gur +haon +hocht +i +iad +idir +in +ina +ins +inár +is +le +leis +lena +lenár +m' +mar +mo +mé +na +nach +naoi +naonúr +ná +ní +níor +nó +nócha +ocht +ochtar +os +roimh +sa +seacht +seachtar +seachtó +seasca +seisear +siad +sibh +sinn +sna +sé +sí +tar +thar +thú +triúr +trí +trína +trínár +tríocha +tú +um +ár +é +éis +í +ó +ón +óna +ónár diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_gl.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_gl.txt new file mode 100644 index 0000000000..d8760b12c1 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_gl.txt @@ -0,0 +1,161 @@ +# galican stopwords +a +aínda +alí +aquel +aquela +aquelas +aqueles +aquilo +aquí +ao +aos +as +así +á +ben +cando +che +co +coa +comigo +con +connosco +contigo +convosco +coas +cos +cun +cuns +cunha +cunhas +da +dalgunha +dalgunhas +dalgún +dalgúns +das +de +del +dela +delas +deles +desde +deste +do +dos +dun +duns +dunha +dunhas +e +el +ela +elas +eles +en +era +eran +esa +esas +ese +eses +esta +estar +estaba +está +están +este +estes +estiven +estou +eu +é +facer +foi +foron +fun +había +hai +iso +isto +la +las +lle +lles +lo +los +mais +me +meu +meus +min +miña +miñas +moi +na +nas +neste +nin +no +non +nos +nosa +nosas +noso +nosos +nós +nun +nunha +nuns +nunhas +o +os +ou +ó +ós +para +pero +pode +pois +pola +polas +polo +polos +por +que +se +senón +ser +seu +seus +sexa +sido +sobre +súa +súas +tamén +tan +te +ten +teñen +teño +ter +teu +teus +ti +tido +tiña +tiven +túa +túas +un +unha +unhas +uns +vos +vosa +vosas +voso +vosos +vós diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_hi.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_hi.txt new file mode 100644 index 0000000000..86286bb083 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_hi.txt @@ -0,0 +1,235 @@ +# Also see http://www.opensource.org/licenses/bsd-license.html +# See http://members.unine.ch/jacques.savoy/clef/index.html. +# This file was created by Jacques Savoy and is distributed under the BSD license. +# Note: by default this file also contains forms normalized by HindiNormalizer +# for spelling variation (see section below), such that it can be used whether or +# not you enable that feature. When adding additional entries to this list, +# please add the normalized form as well. +अंदर +अत +अपना +अपनी +अपने +अभी +आदि +आप +इत्यादि +इन +इनका +इन्हीं +इन्हें +इन्हों +इस +इसका +इसकी +इसके +इसमें +इसी +इसे +उन +उनका +उनकी +उनके +उनको +उन्हीं +उन्हें +उन्हों +उस +उसके +उसी +उसे +एक +एवं +एस +ऐसे +और +कई +कर +करता +करते +करना +करने +करें +कहते +कहा +का +काफ़ी +कि +कितना +किन्हें +किन्हों +किया +किर +किस +किसी +किसे +की +कुछ +कुल +के +को +कोई +कौन +कौनसा +गया +घर +जब +जहाँ +जा +जितना +जिन +जिन्हें +जिन्हों +जिस +जिसे +जीधर +जैसा +जैसे +जो +तक +तब +तरह +तिन +तिन्हें +तिन्हों +तिस +तिसे +तो +था +थी +थे +दबारा +दिया +दुसरा +दूसरे +दो +द्वारा +न +नहीं +ना +निहायत +नीचे +ने +पर +पर +पहले +पूरा +पे +फिर +बनी +बही +बहुत +बाद +बाला +बिलकुल +भी +भीतर +मगर +मानो +मे +में +यदि +यह +यहाँ +यही +या +यिह +ये +रखें +रहा +रहे +ऱ्वासा +लिए +लिये +लेकिन +व +वर्ग +वह +वह +वहाँ +वहीं +वाले +वुह +वे +वग़ैरह +संग +सकता +सकते +सबसे +सभी +साथ +साबुत +साभ +सारा +से +सो +ही +हुआ +हुई +हुए +है +हैं +हो +होता +होती +होते +होना +होने +# additional normalized forms of the above +अपनि +जेसे +होति +सभि +तिंहों +इंहों +दवारा +इसि +किंहें +थि +उंहों +ओर +जिंहें +वहिं +अभि +बनि +हि +उंहिं +उंहें +हें +वगेरह +एसे +रवासा +कोन +निचे +काफि +उसि +पुरा +भितर +हे +बहि +वहां +कोइ +यहां +जिंहों +तिंहें +किसि +कइ +यहि +इंहिं +जिधर +इंहें +अदि +इतयादि +हुइ +कोनसा +इसकि +दुसरे +जहां +अप +किंहों +उनकि +भि +वरग +हुअ +जेसा +नहिं diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_hu.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_hu.txt new file mode 100644 index 0000000000..37526da8aa --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_hu.txt @@ -0,0 +1,211 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/hungarian/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + +| Hungarian stop word list +| prepared by Anna Tordai + +a +ahogy +ahol +aki +akik +akkor +alatt +által +általában +amely +amelyek +amelyekben +amelyeket +amelyet +amelynek +ami +amit +amolyan +amíg +amikor +át +abban +ahhoz +annak +arra +arról +az +azok +azon +azt +azzal +azért +aztán +azután +azonban +bár +be +belül +benne +cikk +cikkek +cikkeket +csak +de +e +eddig +egész +egy +egyes +egyetlen +egyéb +egyik +egyre +ekkor +el +elég +ellen +elő +először +előtt +első +én +éppen +ebben +ehhez +emilyen +ennek +erre +ez +ezt +ezek +ezen +ezzel +ezért +és +fel +felé +hanem +hiszen +hogy +hogyan +igen +így +illetve +ill. +ill +ilyen +ilyenkor +ison +ismét +itt +jó +jól +jobban +kell +kellett +keresztül +keressünk +ki +kívül +között +közül +legalább +lehet +lehetett +legyen +lenne +lenni +lesz +lett +maga +magát +majd +majd +már +más +másik +meg +még +mellett +mert +mely +melyek +mi +mit +míg +miért +milyen +mikor +minden +mindent +mindenki +mindig +mint +mintha +mivel +most +nagy +nagyobb +nagyon +ne +néha +nekem +neki +nem +néhány +nélkül +nincs +olyan +ott +össze +ő +ők +őket +pedig +persze +rá +s +saját +sem +semmi +sok +sokat +sokkal +számára +szemben +szerint +szinte +talán +tehát +teljes +tovább +továbbá +több +úgy +ugyanis +új +újabb +újra +után +utána +utolsó +vagy +vagyis +valaki +valami +valamint +való +vagyok +van +vannak +volt +voltam +voltak +voltunk +vissza +vele +viszont +volna diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_hy.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_hy.txt new file mode 100644 index 0000000000..60c1c50fbc --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_hy.txt @@ -0,0 +1,46 @@ +# example set of Armenian stopwords. +այդ +այլ +այն +այս +դու +դուք +եմ +են +ենք +ես +եք +է +էի +էին +էինք +էիր +էիք +էր +ըստ +թ +ի +ին +իսկ +իր +կամ +համար +հետ +հետո +մենք +մեջ +մի +ն +նա +նաև +նրա +նրանք +որ +որը +որոնք +որպես +ու +ում +պիտի +վրա +և diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_id.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_id.txt new file mode 100644 index 0000000000..4617f83a5c --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_id.txt @@ -0,0 +1,359 @@ +# from appendix D of: A Study of Stemming Effects on Information +# Retrieval in Bahasa Indonesia +ada +adanya +adalah +adapun +agak +agaknya +agar +akan +akankah +akhirnya +aku +akulah +amat +amatlah +anda +andalah +antar +diantaranya +antara +antaranya +diantara +apa +apaan +mengapa +apabila +apakah +apalagi +apatah +atau +ataukah +ataupun +bagai +bagaikan +sebagai +sebagainya +bagaimana +bagaimanapun +sebagaimana +bagaimanakah +bagi +bahkan +bahwa +bahwasanya +sebaliknya +banyak +sebanyak +beberapa +seberapa +begini +beginian +beginikah +beginilah +sebegini +begitu +begitukah +begitulah +begitupun +sebegitu +belum +belumlah +sebelum +sebelumnya +sebenarnya +berapa +berapakah +berapalah +berapapun +betulkah +sebetulnya +biasa +biasanya +bila +bilakah +bisa +bisakah +sebisanya +boleh +bolehkah +bolehlah +buat +bukan +bukankah +bukanlah +bukannya +cuma +percuma +dahulu +dalam +dan +dapat +dari +daripada +dekat +demi +demikian +demikianlah +sedemikian +dengan +depan +di +dia +dialah +dini +diri +dirinya +terdiri +dong +dulu +enggak +enggaknya +entah +entahlah +terhadap +terhadapnya +hal +hampir +hanya +hanyalah +harus +haruslah +harusnya +seharusnya +hendak +hendaklah +hendaknya +hingga +sehingga +ia +ialah +ibarat +ingin +inginkah +inginkan +ini +inikah +inilah +itu +itukah +itulah +jangan +jangankan +janganlah +jika +jikalau +juga +justru +kala +kalau +kalaulah +kalaupun +kalian +kami +kamilah +kamu +kamulah +kan +kapan +kapankah +kapanpun +dikarenakan +karena +karenanya +ke +kecil +kemudian +kenapa +kepada +kepadanya +ketika +seketika +khususnya +kini +kinilah +kiranya +sekiranya +kita +kitalah +kok +lagi +lagian +selagi +lah +lain +lainnya +melainkan +selaku +lalu +melalui +terlalu +lama +lamanya +selama +selama +selamanya +lebih +terlebih +bermacam +macam +semacam +maka +makanya +makin +malah +malahan +mampu +mampukah +mana +manakala +manalagi +masih +masihkah +semasih +masing +mau +maupun +semaunya +memang +mereka +merekalah +meski +meskipun +semula +mungkin +mungkinkah +nah +namun +nanti +nantinya +nyaris +oleh +olehnya +seorang +seseorang +pada +padanya +padahal +paling +sepanjang +pantas +sepantasnya +sepantasnyalah +para +pasti +pastilah +per +pernah +pula +pun +merupakan +rupanya +serupa +saat +saatnya +sesaat +saja +sajalah +saling +bersama +sama +sesama +sambil +sampai +sana +sangat +sangatlah +saya +sayalah +se +sebab +sebabnya +sebuah +tersebut +tersebutlah +sedang +sedangkan +sedikit +sedikitnya +segala +segalanya +segera +sesegera +sejak +sejenak +sekali +sekalian +sekalipun +sesekali +sekaligus +sekarang +sekarang +sekitar +sekitarnya +sela +selain +selalu +seluruh +seluruhnya +semakin +sementara +sempat +semua +semuanya +sendiri +sendirinya +seolah +seperti +sepertinya +sering +seringnya +serta +siapa +siapakah +siapapun +disini +disinilah +sini +sinilah +sesuatu +sesuatunya +suatu +sesudah +sesudahnya +sudah +sudahkah +sudahlah +supaya +tadi +tadinya +tak +tanpa +setelah +telah +tentang +tentu +tentulah +tentunya +tertentu +seterusnya +tapi +tetapi +setiap +tiap +setidaknya +tidak +tidakkah +tidaklah +toh +waduh +wah +wahai +sewaktu +walau +walaupun +wong +yaitu +yakni +yang diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_it.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_it.txt new file mode 100644 index 0000000000..1219cc773a --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_it.txt @@ -0,0 +1,303 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/italian/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | An Italian stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + +ad | a (to) before vowel +al | a + il +allo | a + lo +ai | a + i +agli | a + gli +all | a + l' +agl | a + gl' +alla | a + la +alle | a + le +con | with +col | con + il +coi | con + i (forms collo, cogli etc are now very rare) +da | from +dal | da + il +dallo | da + lo +dai | da + i +dagli | da + gli +dall | da + l' +dagl | da + gll' +dalla | da + la +dalle | da + le +di | of +del | di + il +dello | di + lo +dei | di + i +degli | di + gli +dell | di + l' +degl | di + gl' +della | di + la +delle | di + le +in | in +nel | in + el +nello | in + lo +nei | in + i +negli | in + gli +nell | in + l' +negl | in + gl' +nella | in + la +nelle | in + le +su | on +sul | su + il +sullo | su + lo +sui | su + i +sugli | su + gli +sull | su + l' +sugl | su + gl' +sulla | su + la +sulle | su + le +per | through, by +tra | among +contro | against +io | I +tu | thou +lui | he +lei | she +noi | we +voi | you +loro | they +mio | my +mia | +miei | +mie | +tuo | +tua | +tuoi | thy +tue | +suo | +sua | +suoi | his, her +sue | +nostro | our +nostra | +nostri | +nostre | +vostro | your +vostra | +vostri | +vostre | +mi | me +ti | thee +ci | us, there +vi | you, there +lo | him, the +la | her, the +li | them +le | them, the +gli | to him, the +ne | from there etc +il | the +un | a +uno | a +una | a +ma | but +ed | and +se | if +perché | why, because +anche | also +come | how +dov | where (as dov') +dove | where +che | who, that +chi | who +cui | whom +non | not +più | more +quale | who, that +quanto | how much +quanti | +quanta | +quante | +quello | that +quelli | +quella | +quelle | +questo | this +questi | +questa | +queste | +si | yes +tutto | all +tutti | all + + | single letter forms: + +a | at +c | as c' for ce or ci +e | and +i | the +l | as l' +o | or + + | forms of avere, to have (not including the infinitive): + +ho +hai +ha +abbiamo +avete +hanno +abbia +abbiate +abbiano +avrò +avrai +avrà +avremo +avrete +avranno +avrei +avresti +avrebbe +avremmo +avreste +avrebbero +avevo +avevi +aveva +avevamo +avevate +avevano +ebbi +avesti +ebbe +avemmo +aveste +ebbero +avessi +avesse +avessimo +avessero +avendo +avuto +avuta +avuti +avute + + | forms of essere, to be (not including the infinitive): +sono +sei +è +siamo +siete +sia +siate +siano +sarò +sarai +sarà +saremo +sarete +saranno +sarei +saresti +sarebbe +saremmo +sareste +sarebbero +ero +eri +era +eravamo +eravate +erano +fui +fosti +fu +fummo +foste +furono +fossi +fosse +fossimo +fossero +essendo + + | forms of fare, to do (not including the infinitive, fa, fat-): +faccio +fai +facciamo +fanno +faccia +facciate +facciano +farò +farai +farà +faremo +farete +faranno +farei +faresti +farebbe +faremmo +fareste +farebbero +facevo +facevi +faceva +facevamo +facevate +facevano +feci +facesti +fece +facemmo +faceste +fecero +facessi +facesse +facessimo +facessero +facendo + + | forms of stare, to be (not including the infinitive): +sto +stai +sta +stiamo +stanno +stia +stiate +stiano +starò +starai +starà +staremo +starete +staranno +starei +staresti +starebbe +staremmo +stareste +starebbero +stavo +stavi +stava +stavamo +stavate +stavano +stetti +stesti +stette +stemmo +steste +stettero +stessi +stesse +stessimo +stessero +stando diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ja.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ja.txt new file mode 100644 index 0000000000..d4321be6b1 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_ja.txt @@ -0,0 +1,127 @@ +# +# This file defines a stopword set for Japanese. +# +# This set is made up of hand-picked frequent terms from segmented Japanese Wikipedia. +# Punctuation characters and frequent kanji have mostly been left out. See LUCENE-3745 +# for frequency lists, etc. that can be useful for making your own set (if desired) +# +# Note that there is an overlap between these stopwords and the terms stopped when used +# in combination with the JapanesePartOfSpeechStopFilter. When editing this file, note +# that comments are not allowed on the same line as stopwords. +# +# Also note that stopping is done in a case-insensitive manner. Change your StopFilter +# configuration if you need case-sensitive stopping. Lastly, note that stopping is done +# using the same character width as the entries in this file. Since this StopFilter is +# normally done after a CJKWidthFilter in your chain, you would usually want your romaji +# entries to be in half-width and your kana entries to be in full-width. +# +の +に +は +を +た +が +で +て +と +し +れ +さ +ある +いる +も +する +から +な +こと +として +い +や +れる +など +なっ +ない +この +ため +その +あっ +よう +また +もの +という +あり +まで +られ +なる +へ +か +だ +これ +によって +により +おり +より +による +ず +なり +られる +において +ば +なかっ +なく +しかし +について +せ +だっ +その後 +できる +それ +う +ので +なお +のみ +でき +き +つ +における +および +いう +さらに +でも +ら +たり +その他 +に関する +たち +ます +ん +なら +に対して +特に +せる +及び +これら +とき +では +にて +ほか +ながら +うち +そして +とともに +ただし +かつて +それぞれ +または +お +ほど +ものの +に対する +ほとんど +と共に +といった +です +とも +ところ +ここ +##### End of file diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_lv.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_lv.txt new file mode 100644 index 0000000000..e21a23c06c --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_lv.txt @@ -0,0 +1,172 @@ +# Set of Latvian stopwords from A Stemming Algorithm for Latvian, Karlis Kreslins +# the original list of over 800 forms was refined: +# pronouns, adverbs, interjections were removed +# +# prepositions +aiz +ap +ar +apakš +ārpus +augšpus +bez +caur +dēļ +gar +iekš +iz +kopš +labad +lejpus +līdz +no +otrpus +pa +par +pār +pēc +pie +pirms +pret +priekš +starp +šaipus +uz +viņpus +virs +virspus +zem +apakšpus +# Conjunctions +un +bet +jo +ja +ka +lai +tomēr +tikko +turpretī +arī +kaut +gan +tādēļ +tā +ne +tikvien +vien +kā +ir +te +vai +kamēr +# Particles +ar +diezin +droši +diemžēl +nebūt +ik +it +taču +nu +pat +tiklab +iekšpus +nedz +tik +nevis +turpretim +jeb +iekam +iekām +iekāms +kolīdz +līdzko +tiklīdz +jebšu +tālab +tāpēc +nekā +itin +jā +jau +jel +nē +nezin +tad +tikai +vis +tak +iekams +vien +# modal verbs +būt +biju +biji +bija +bijām +bijāt +esmu +esi +esam +esat +būšu +būsi +būs +būsim +būsiet +tikt +tiku +tiki +tika +tikām +tikāt +tieku +tiec +tiek +tiekam +tiekat +tikšu +tiks +tiksim +tiksiet +tapt +tapi +tapāt +topat +tapšu +tapsi +taps +tapsim +tapsiet +kļūt +kļuvu +kļuvi +kļuva +kļuvām +kļuvāt +kļūstu +kļūsti +kļūst +kļūstam +kļūstat +kļūšu +kļūsi +kļūs +kļūsim +kļūsiet +# verbs +varēt +varēju +varējām +varēšu +varēsim +var +varēji +varējāt +varēsi +varēsiet +varat +varēja +varēs diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_nl.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_nl.txt new file mode 100644 index 0000000000..47a2aeacf6 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_nl.txt @@ -0,0 +1,119 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/dutch/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | A Dutch stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + + | This is a ranked list (commonest to rarest) of stopwords derived from + | a large sample of Dutch text. + + | Dutch stop words frequently exhibit homonym clashes. These are indicated + | clearly below. + +de | the +en | and +van | of, from +ik | I, the ego +te | (1) chez, at etc, (2) to, (3) too +dat | that, which +die | that, those, who, which +in | in, inside +een | a, an, one +hij | he +het | the, it +niet | not, nothing, naught +zijn | (1) to be, being, (2) his, one's, its +is | is +was | (1) was, past tense of all persons sing. of 'zijn' (to be) (2) wax, (3) the washing, (4) rise of river +op | on, upon, at, in, up, used up +aan | on, upon, to (as dative) +met | with, by +als | like, such as, when +voor | (1) before, in front of, (2) furrow +had | had, past tense all persons sing. of 'hebben' (have) +er | there +maar | but, only +om | round, about, for etc +hem | him +dan | then +zou | should/would, past tense all persons sing. of 'zullen' +of | or, whether, if +wat | what, something, anything +mijn | possessive and noun 'mine' +men | people, 'one' +dit | this +zo | so, thus, in this way +door | through by +over | over, across +ze | she, her, they, them +zich | oneself +bij | (1) a bee, (2) by, near, at +ook | also, too +tot | till, until +je | you +mij | me +uit | out of, from +der | Old Dutch form of 'van der' still found in surnames +daar | (1) there, (2) because +haar | (1) her, their, them, (2) hair +naar | (1) unpleasant, unwell etc, (2) towards, (3) as +heb | present first person sing. of 'to have' +hoe | how, why +heeft | present third person sing. of 'to have' +hebben | 'to have' and various parts thereof +deze | this +u | you +want | (1) for, (2) mitten, (3) rigging +nog | yet, still +zal | 'shall', first and third person sing. of verb 'zullen' (will) +me | me +zij | she, they +nu | now +ge | 'thou', still used in Belgium and south Netherlands +geen | none +omdat | because +iets | something, somewhat +worden | to become, grow, get +toch | yet, still +al | all, every, each +waren | (1) 'were' (2) to wander, (3) wares, (3) +veel | much, many +meer | (1) more, (2) lake +doen | to do, to make +toen | then, when +moet | noun 'spot/mote' and present form of 'to must' +ben | (1) am, (2) 'are' in interrogative second person singular of 'to be' +zonder | without +kan | noun 'can' and present form of 'to be able' +hun | their, them +dus | so, consequently +alles | all, everything, anything +onder | under, beneath +ja | yes, of course +eens | once, one day +hier | here +wie | who +werd | imperfect third person sing. of 'become' +altijd | always +doch | yet, but etc +wordt | present third person sing. of 'become' +wezen | (1) to be, (2) 'been' as in 'been fishing', (3) orphans +kunnen | to be able +ons | us/our +zelf | self +tegen | against, towards, at +na | after, near +reeds | already +wil | (1) present tense of 'want', (2) 'will', noun, (3) fender +kon | could; past tense of 'to be able' +niets | nothing +uw | your +iemand | somebody +geweest | been; past participle of 'be' +andere | other diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_no.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_no.txt new file mode 100644 index 0000000000..a7a2c28ba5 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_no.txt @@ -0,0 +1,194 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/norwegian/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | A Norwegian stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + + | This stop word list is for the dominant bokmål dialect. Words unique + | to nynorsk are marked *. + + | Revised by Jan Bruusgaard , Jan 2005 + +og | and +i | in +jeg | I +det | it/this/that +at | to (w. inf.) +en | a/an +et | a/an +den | it/this/that +til | to +er | is/am/are +som | who/that +på | on +de | they / you(formal) +med | with +han | he +av | of +ikke | not +ikkje | not * +der | there +så | so +var | was/were +meg | me +seg | you +men | but +ett | one +har | have +om | about +vi | we +min | my +mitt | my +ha | have +hadde | had +hun | she +nå | now +over | over +da | when/as +ved | by/know +fra | from +du | you +ut | out +sin | your +dem | them +oss | us +opp | up +man | you/one +kan | can +hans | his +hvor | where +eller | or +hva | what +skal | shall/must +selv | self (reflective) +sjøl | self (reflective) +her | here +alle | all +vil | will +bli | become +ble | became +blei | became * +blitt | have become +kunne | could +inn | in +når | when +være | be +kom | come +noen | some +noe | some +ville | would +dere | you +som | who/which/that +deres | their/theirs +kun | only/just +ja | yes +etter | after +ned | down +skulle | should +denne | this +for | for/because +deg | you +si | hers/his +sine | hers/his +sitt | hers/his +mot | against +å | to +meget | much +hvorfor | why +dette | this +disse | these/those +uten | without +hvordan | how +ingen | none +din | your +ditt | your +blir | become +samme | same +hvilken | which +hvilke | which (plural) +sånn | such a +inni | inside/within +mellom | between +vår | our +hver | each +hvem | who +vors | us/ours +hvis | whose +både | both +bare | only/just +enn | than +fordi | as/because +før | before +mange | many +også | also +slik | just +vært | been +være | to be +båe | both * +begge | both +siden | since +dykk | your * +dykkar | yours * +dei | they * +deira | them * +deires | theirs * +deim | them * +di | your (fem.) * +då | as/when * +eg | I * +ein | a/an * +eit | a/an * +eitt | a/an * +elles | or * +honom | he * +hjå | at * +ho | she * +hoe | she * +henne | her +hennar | her/hers +hennes | hers +hoss | how * +hossen | how * +ikkje | not * +ingi | noone * +inkje | noone * +korleis | how * +korso | how * +kva | what/which * +kvar | where * +kvarhelst | where * +kven | who/whom * +kvi | why * +kvifor | why * +me | we * +medan | while * +mi | my * +mine | my * +mykje | much * +no | now * +nokon | some (masc./neut.) * +noka | some (fem.) * +nokor | some * +noko | some * +nokre | some * +si | his/hers * +sia | since * +sidan | since * +so | so * +somt | some * +somme | some * +um | about* +upp | up * +vere | be * +vore | was * +verte | become * +vort | become * +varte | became * +vart | became * + diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_pt.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_pt.txt new file mode 100644 index 0000000000..acfeb01af6 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_pt.txt @@ -0,0 +1,253 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/portuguese/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | A Portuguese stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + + + | The following is a ranked list (commonest to rarest) of stopwords + | deriving from a large sample of text. + + | Extra words have been added at the end. + +de | of, from +a | the; to, at; her +o | the; him +que | who, that +e | and +do | de + o +da | de + a +em | in +um | a +para | for + | é from SER +com | with +não | not, no +uma | a +os | the; them +no | em + o +se | himself etc +na | em + a +por | for +mais | more +as | the; them +dos | de + os +como | as, like +mas | but + | foi from SER +ao | a + o +ele | he +das | de + as + | tem from TER +à | a + a +seu | his +sua | her +ou | or + | ser from SER +quando | when +muito | much + | há from HAV +nos | em + os; us +já | already, now + | está from EST +eu | I +também | also +só | only, just +pelo | per + o +pela | per + a +até | up to +isso | that +ela | he +entre | between + | era from SER +depois | after +sem | without +mesmo | same +aos | a + os + | ter from TER +seus | his +quem | whom +nas | em + as +me | me +esse | that +eles | they + | estão from EST +você | you + | tinha from TER + | foram from SER +essa | that +num | em + um +nem | nor +suas | her +meu | my +às | a + as +minha | my + | têm from TER +numa | em + uma +pelos | per + os +elas | they + | havia from HAV + | seja from SER +qual | which + | será from SER +nós | we + | tenho from TER +lhe | to him, her +deles | of them +essas | those +esses | those +pelas | per + as +este | this + | fosse from SER +dele | of him + + | other words. There are many contractions such as naquele = em+aquele, + | mo = me+o, but they are rare. + | Indefinite article plural forms are also rare. + +tu | thou +te | thee +vocês | you (plural) +vos | you +lhes | to them +meus | my +minhas +teu | thy +tua +teus +tuas +nosso | our +nossa +nossos +nossas + +dela | of her +delas | of them + +esta | this +estes | these +estas | these +aquele | that +aquela | that +aqueles | those +aquelas | those +isto | this +aquilo | that + + | forms of estar, to be (not including the infinitive): +estou +está +estamos +estão +estive +esteve +estivemos +estiveram +estava +estávamos +estavam +estivera +estivéramos +esteja +estejamos +estejam +estivesse +estivéssemos +estivessem +estiver +estivermos +estiverem + + | forms of haver, to have (not including the infinitive): +hei +há +havemos +hão +houve +houvemos +houveram +houvera +houvéramos +haja +hajamos +hajam +houvesse +houvéssemos +houvessem +houver +houvermos +houverem +houverei +houverá +houveremos +houverão +houveria +houveríamos +houveriam + + | forms of ser, to be (not including the infinitive): +sou +somos +são +era +éramos +eram +fui +foi +fomos +foram +fora +fôramos +seja +sejamos +sejam +fosse +fôssemos +fossem +for +formos +forem +serei +será +seremos +serão +seria +seríamos +seriam + + | forms of ter, to have (not including the infinitive): +tenho +tem +temos +tém +tinha +tínhamos +tinham +tive +teve +tivemos +tiveram +tivera +tivéramos +tenha +tenhamos +tenham +tivesse +tivéssemos +tivessem +tiver +tivermos +tiverem +terei +terá +teremos +terão +teria +teríamos +teriam diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ro.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ro.txt new file mode 100644 index 0000000000..4fdee90a5b --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_ro.txt @@ -0,0 +1,233 @@ +# This file was created by Jacques Savoy and is distributed under the BSD license. +# See http://members.unine.ch/jacques.savoy/clef/index.html. +# Also see http://www.opensource.org/licenses/bsd-license.html +acea +aceasta +această +aceea +acei +aceia +acel +acela +acele +acelea +acest +acesta +aceste +acestea +aceşti +aceştia +acolo +acum +ai +aia +aibă +aici +al +ăla +ale +alea +ălea +altceva +altcineva +am +ar +are +aş +aşadar +asemenea +asta +ăsta +astăzi +astea +ăstea +ăştia +asupra +aţi +au +avea +avem +aveţi +azi +bine +bucur +bună +ca +că +căci +când +care +cărei +căror +cărui +cât +câte +câţi +către +câtva +ce +cel +ceva +chiar +cînd +cine +cineva +cît +cîte +cîţi +cîtva +contra +cu +cum +cumva +curând +curînd +da +dă +dacă +dar +datorită +de +deci +deja +deoarece +departe +deşi +din +dinaintea +dintr +dintre +drept +după +ea +ei +el +ele +eram +este +eşti +eu +face +fără +fi +fie +fiecare +fii +fim +fiţi +iar +ieri +îi +îl +îmi +împotriva +în +înainte +înaintea +încât +încît +încotro +între +întrucât +întrucît +îţi +la +lângă +le +li +lîngă +lor +lui +mă +mâine +mea +mei +mele +mereu +meu +mi +mine +mult +multă +mulţi +ne +nicăieri +nici +nimeni +nişte +noastră +noastre +noi +noştri +nostru +nu +ori +oricând +oricare +oricât +orice +oricînd +oricine +oricît +oricum +oriunde +până +pe +pentru +peste +pînă +poate +pot +prea +prima +primul +prin +printr +sa +să +săi +sale +sau +său +se +şi +sînt +sîntem +sînteţi +spre +sub +sunt +suntem +sunteţi +ta +tăi +tale +tău +te +ţi +ţie +tine +toată +toate +tot +toţi +totuşi +tu +un +una +unde +undeva +unei +unele +uneori +unor +vă +vi +voastră +voastre +voi +voştri +vostru +vouă +vreo +vreun diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ru.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ru.txt new file mode 100644 index 0000000000..55271400c6 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_ru.txt @@ -0,0 +1,243 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/russian/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | a russian stop word list. comments begin with vertical bar. each stop + | word is at the start of a line. + + | this is a ranked list (commonest to rarest) of stopwords derived from + | a large text sample. + + | letter `ё' is translated to `е'. + +и | and +в | in/into +во | alternative form +не | not +что | what/that +он | he +на | on/onto +я | i +с | from +со | alternative form +как | how +а | milder form of `no' (but) +то | conjunction and form of `that' +все | all +она | she +так | so, thus +его | him +но | but +да | yes/and +ты | thou +к | towards, by +у | around, chez +же | intensifier particle +вы | you +за | beyond, behind +бы | conditional/subj. particle +по | up to, along +только | only +ее | her +мне | to me +было | it was +вот | here is/are, particle +от | away from +меня | me +еще | still, yet, more +нет | no, there isnt/arent +о | about +из | out of +ему | to him +теперь | now +когда | when +даже | even +ну | so, well +вдруг | suddenly +ли | interrogative particle +если | if +уже | already, but homonym of `narrower' +или | or +ни | neither +быть | to be +был | he was +него | prepositional form of его +до | up to +вас | you accusative +нибудь | indef. suffix preceded by hyphen +опять | again +уж | already, but homonym of `adder' +вам | to you +сказал | he said +ведь | particle `after all' +там | there +потом | then +себя | oneself +ничего | nothing +ей | to her +может | usually with `быть' as `maybe' +они | they +тут | here +где | where +есть | there is/are +надо | got to, must +ней | prepositional form of ей +для | for +мы | we +тебя | thee +их | them, their +чем | than +была | she was +сам | self +чтоб | in order to +без | without +будто | as if +человек | man, person, one +чего | genitive form of `what' +раз | once +тоже | also +себе | to oneself +под | beneath +жизнь | life +будет | will be +ж | short form of intensifer particle `же' +тогда | then +кто | who +этот | this +говорил | was saying +того | genitive form of `that' +потому | for that reason +этого | genitive form of `this' +какой | which +совсем | altogether +ним | prepositional form of `его', `они' +здесь | here +этом | prepositional form of `этот' +один | one +почти | almost +мой | my +тем | instrumental/dative plural of `тот', `то' +чтобы | full form of `in order that' +нее | her (acc.) +кажется | it seems +сейчас | now +были | they were +куда | where to +зачем | why +сказать | to say +всех | all (acc., gen. preposn. plural) +никогда | never +сегодня | today +можно | possible, one can +при | by +наконец | finally +два | two +об | alternative form of `о', about +другой | another +хоть | even +после | after +над | above +больше | more +тот | that one (masc.) +через | across, in +эти | these +нас | us +про | about +всего | in all, only, of all +них | prepositional form of `они' (they) +какая | which, feminine +много | lots +разве | interrogative particle +сказала | she said +три | three +эту | this, acc. fem. sing. +моя | my, feminine +впрочем | moreover, besides +хорошо | good +свою | ones own, acc. fem. sing. +этой | oblique form of `эта', fem. `this' +перед | in front of +иногда | sometimes +лучше | better +чуть | a little +том | preposn. form of `that one' +нельзя | one must not +такой | such a one +им | to them +более | more +всегда | always +конечно | of course +всю | acc. fem. sing of `all' +между | between + + + | b: some paradigms + | + | personal pronouns + | + | я меня мне мной [мною] + | ты тебя тебе тобой [тобою] + | он его ему им [него, нему, ним] + | она ее эи ею [нее, нэи, нею] + | оно его ему им [него, нему, ним] + | + | мы нас нам нами + | вы вас вам вами + | они их им ими [них, ним, ними] + | + | себя себе собой [собою] + | + | demonstrative pronouns: этот (this), тот (that) + | + | этот эта это эти + | этого эты это эти + | этого этой этого этих + | этому этой этому этим + | этим этой этим [этою] этими + | этом этой этом этих + | + | тот та то те + | того ту то те + | того той того тех + | тому той тому тем + | тем той тем [тою] теми + | том той том тех + | + | determinative pronouns + | + | (a) весь (all) + | + | весь вся все все + | всего всю все все + | всего всей всего всех + | всему всей всему всем + | всем всей всем [всею] всеми + | всем всей всем всех + | + | (b) сам (himself etc) + | + | сам сама само сами + | самого саму само самих + | самого самой самого самих + | самому самой самому самим + | самим самой самим [самою] самими + | самом самой самом самих + | + | stems of verbs `to be', `to have', `to do' and modal + | + | быть бы буд быв есть суть + | име + | дел + | мог мож мочь + | уме + | хоч хот + | долж + | можн + | нужн + | нельзя + diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_sv.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_sv.txt new file mode 100644 index 0000000000..096f87f676 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_sv.txt @@ -0,0 +1,133 @@ + | From svn.tartarus.org/snowball/trunk/website/algorithms/swedish/stop.txt + | This file is distributed under the BSD License. + | See http://snowball.tartarus.org/license.php + | Also see http://www.opensource.org/licenses/bsd-license.html + | - Encoding was converted to UTF-8. + | - This notice was added. + | + | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" + + | A Swedish stop word list. Comments begin with vertical bar. Each stop + | word is at the start of a line. + + | This is a ranked list (commonest to rarest) of stopwords derived from + | a large text sample. + + | Swedish stop words occasionally exhibit homonym clashes. For example + | så = so, but also seed. These are indicated clearly below. + +och | and +det | it, this/that +att | to (with infinitive) +i | in, at +en | a +jag | I +hon | she +som | who, that +han | he +på | on +den | it, this/that +med | with +var | where, each +sig | him(self) etc +för | for +så | so (also: seed) +till | to +är | is +men | but +ett | a +om | if; around, about +hade | had +de | they, these/those +av | of +icke | not, no +mig | me +du | you +henne | her +då | then, when +sin | his +nu | now +har | have +inte | inte någon = no one +hans | his +honom | him +skulle | 'sake' +hennes | her +där | there +min | my +man | one (pronoun) +ej | nor +vid | at, by, on (also: vast) +kunde | could +något | some etc +från | from, off +ut | out +när | when +efter | after, behind +upp | up +vi | we +dem | them +vara | be +vad | what +över | over +än | than +dig | you +kan | can +sina | his +här | here +ha | have +mot | towards +alla | all +under | under (also: wonder) +någon | some etc +eller | or (else) +allt | all +mycket | much +sedan | since +ju | why +denna | this/that +själv | myself, yourself etc +detta | this/that +åt | to +utan | without +varit | was +hur | how +ingen | no +mitt | my +ni | you +bli | to be, become +blev | from bli +oss | us +din | thy +dessa | these/those +några | some etc +deras | their +blir | from bli +mina | my +samma | (the) same +vilken | who, that +er | you, your +sådan | such a +vår | our +blivit | from bli +dess | its +inom | within +mellan | between +sådant | such a +varför | why +varje | each +vilka | who, that +ditt | thy +vem | who +vilket | who, that +sitta | his +sådana | such a +vart | each +dina | thy +vars | whose +vårt | our +våra | our +ert | your +era | your +vilkas | whose + diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_th.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_th.txt new file mode 100644 index 0000000000..07f0fabe69 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_th.txt @@ -0,0 +1,119 @@ +# Thai stopwords from: +# "Opinion Detection in Thai Political News Columns +# Based on Subjectivity Analysis" +# Khampol Sukhum, Supot Nitsuwat, and Choochart Haruechaiyasak +ไว้ +ไม่ +ไป +ได้ +ให้ +ใน +โดย +แห่ง +แล้ว +และ +แรก +แบบ +แต่ +เอง +เห็น +เลย +เริ่ม +เรา +เมื่อ +เพื่อ +เพราะ +เป็นการ +เป็น +เปิดเผย +เปิด +เนื่องจาก +เดียวกัน +เดียว +เช่น +เฉพาะ +เคย +เข้า +เขา +อีก +อาจ +อะไร +ออก +อย่าง +อยู่ +อยาก +หาก +หลาย +หลังจาก +หลัง +หรือ +หนึ่ง +ส่วน +ส่ง +สุด +สําหรับ +ว่า +วัน +ลง +ร่วม +ราย +รับ +ระหว่าง +รวม +ยัง +มี +มาก +มา +พร้อม +พบ +ผ่าน +ผล +บาง +น่า +นี้ +นํา +นั้น +นัก +นอกจาก +ทุก +ที่สุด +ที่ +ทําให้ +ทํา +ทาง +ทั้งนี้ +ทั้ง +ถ้า +ถูก +ถึง +ต้อง +ต่างๆ +ต่าง +ต่อ +ตาม +ตั้งแต่ +ตั้ง +ด้าน +ด้วย +ดัง +ซึ่ง +ช่วง +จึง +จาก +จัด +จะ +คือ +ความ +ครั้ง +คง +ขึ้น +ของ +ขอ +ขณะ +ก่อน +ก็ +การ +กับ +กัน +กว่า +กล่าว diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_tr.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_tr.txt new file mode 100644 index 0000000000..84d9408d4e --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/stopwords_tr.txt @@ -0,0 +1,212 @@ +# Turkish stopwords from LUCENE-559 +# merged with the list from "Information Retrieval on Turkish Texts" +# (http://www.users.muohio.edu/canf/papers/JASIST2008offPrint.pdf) +acaba +altmış +altı +ama +ancak +arada +aslında +ayrıca +bana +bazı +belki +ben +benden +beni +benim +beri +beş +bile +bin +bir +birçok +biri +birkaç +birkez +birşey +birşeyi +biz +bize +bizden +bizi +bizim +böyle +böylece +bu +buna +bunda +bundan +bunlar +bunları +bunların +bunu +bunun +burada +çok +çünkü +da +daha +dahi +de +defa +değil +diğer +diye +doksan +dokuz +dolayı +dolayısıyla +dört +edecek +eden +ederek +edilecek +ediliyor +edilmesi +ediyor +eğer +elli +en +etmesi +etti +ettiği +ettiğini +gibi +göre +halen +hangi +hatta +hem +henüz +hep +hepsi +her +herhangi +herkesin +hiç +hiçbir +için +iki +ile +ilgili +ise +işte +itibaren +itibariyle +kadar +karşın +katrilyon +kendi +kendilerine +kendini +kendisi +kendisine +kendisini +kez +ki +kim +kimden +kime +kimi +kimse +kırk +milyar +milyon +mu +mü +mı +nasıl +ne +neden +nedenle +nerde +nerede +nereye +niye +niçin +o +olan +olarak +oldu +olduğu +olduğunu +olduklarını +olmadı +olmadığı +olmak +olması +olmayan +olmaz +olsa +olsun +olup +olur +olursa +oluyor +on +ona +ondan +onlar +onlardan +onları +onların +onu +onun +otuz +oysa +öyle +pek +rağmen +sadece +sanki +sekiz +seksen +sen +senden +seni +senin +siz +sizden +sizi +sizin +şey +şeyden +şeyi +şeyler +şöyle +şu +şuna +şunda +şundan +şunları +şunu +tarafından +trilyon +tüm +üç +üzere +var +vardı +ve +veya +ya +yani +yapacak +yapılan +yapılması +yapıyor +yapmak +yaptı +yaptığı +yaptığını +yaptıkları +yedi +yerine +yetmiş +yine +yirmi +yoksa +yüz +zaten diff --git a/dspace/solr/word_highlighting/conf/lang/userdict_ja.txt b/dspace/solr/word_highlighting/conf/lang/userdict_ja.txt new file mode 100644 index 0000000000..6f0368e4d8 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/lang/userdict_ja.txt @@ -0,0 +1,29 @@ +# +# This is a sample user dictionary for Kuromoji (JapaneseTokenizer) +# +# Add entries to this file in order to override the statistical model in terms +# of segmentation, readings and part-of-speech tags. Notice that entries do +# not have weights since they are always used when found. This is by-design +# in order to maximize ease-of-use. +# +# Entries are defined using the following CSV format: +# , ... , ... , +# +# Notice that a single half-width space separates tokens and readings, and +# that the number tokens and readings must match exactly. +# +# Also notice that multiple entries with the same is undefined. +# +# Whitespace only lines are ignored. Comments are not allowed on entry lines. +# + +# Custom segmentation for kanji compounds +日本経済新聞,日本 経済 新聞,ニホン ケイザイ シンブン,カスタム名詞 +関西国際空港,関西 国際 空港,カンサイ コクサイ クウコウ,カスタム名詞 + +# Custom segmentation for compound katakana +トートバッグ,トート バッグ,トート バッグ,かずカナ名詞 +ショルダーバッグ,ショルダー バッグ,ショルダー バッグ,かずカナ名詞 + +# Custom reading for former sumo wrestler +朝青龍,朝青龍,アサショウリュウ,カスタム人名 diff --git a/dspace/solr/word_highlighting/conf/managed-schema b/dspace/solr/word_highlighting/conf/managed-schema new file mode 100644 index 0000000000..5a37069bd6 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/managed-schema @@ -0,0 +1,543 @@ + + + + id + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/solr/word_highlighting/conf/protwords.txt b/dspace/solr/word_highlighting/conf/protwords.txt new file mode 100644 index 0000000000..1dfc0abecb --- /dev/null +++ b/dspace/solr/word_highlighting/conf/protwords.txt @@ -0,0 +1,21 @@ +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#----------------------------------------------------------------------- +# Use a protected word file to protect against the stemmer reducing two +# unrelated words to the same base word. + +# Some non-words that normally won't be encountered, +# just to test that they won't be stemmed. +dontstems +zwhacky + diff --git a/dspace/solr/word_highlighting/conf/solrconfig.xml b/dspace/solr/word_highlighting/conf/solrconfig.xml new file mode 100644 index 0000000000..ccc149c51a --- /dev/null +++ b/dspace/solr/word_highlighting/conf/solrconfig.xml @@ -0,0 +1,1313 @@ + + + + + + + + + 8.8.1 + + + + + + + + + + + + ${solr.data.dir:} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${solr.lock.type:native} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${solr.ulog.dir:} + ${solr.ulog.numVersionBuckets:65536} + + + + + ${solr.autoCommit.maxTime:15000} + false + + + + + + ${solr.autoSoftCommit.maxTime:-1} + + + + + + + + + + + + + + ${solr.max.booleanClauses:1024} + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + 20 + + + 200 + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + explicit + 10 + + + + + + + + + + + + + + + + explicit + json + true + + + + + + _text_ + + + + + + + + + text_general + + + + + + default + _text_ + solr.DirectSolrSpellChecker + + internal + + 0.5 + + 2 + + 1 + + 5 + + 4 + + 0.01 + + + + + + + + + + + + default + on + true + 10 + 5 + 5 + true + true + 10 + 5 + + + spellcheck + + + + + + + + + + true + false + + + terms + + + + + + + query + ocrHighlight + highlight + + + + + + + + + + + 100 + + + + + + + + 70 + + 0.5 + + [-\w ,/\n\"']{20,200} + + + + + + + ]]> + ]]> + + + + + + + + + + + + + + + + + + + + + + + + ,, + ,, + ,, + ,, + ,]]> + ]]> + + + + + + 10 + .,!? + + + + + + + WORD + + + en + US + + + + + + + + + + + + [^\w-\.] + _ + + + + + + + yyyy-MM-dd['T'[HH:mm[:ss[.SSS]][z + yyyy-MM-dd['T'[HH:mm[:ss[,SSS]][z + yyyy-MM-dd HH:mm[:ss[.SSS]][z + yyyy-MM-dd HH:mm[:ss[,SSS]][z + [EEE, ]dd MMM yyyy HH:mm[:ss] z + EEEE, dd-MMM-yy HH:mm:ss z + EEE MMM ppd HH:mm:ss [z ]yyyy + + + + + java.lang.String + text_general + + *_str + 256 + + + true + + + java.lang.Boolean + booleans + + + java.util.Date + pdates + + + java.lang.Long + java.lang.Integer + plongs + + + java.lang.Number + pdoubles + + + + + + + + + + + + + + + + + + + + text/plain; charset=UTF-8 + + + + + + + + + + + + + + diff --git a/dspace/solr/word_highlighting/conf/stopwords.txt b/dspace/solr/word_highlighting/conf/stopwords.txt new file mode 100644 index 0000000000..ae1e83eeb3 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/stopwords.txt @@ -0,0 +1,14 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/dspace/solr/word_highlighting/conf/synonyms.txt b/dspace/solr/word_highlighting/conf/synonyms.txt new file mode 100644 index 0000000000..eab4ee8753 --- /dev/null +++ b/dspace/solr/word_highlighting/conf/synonyms.txt @@ -0,0 +1,29 @@ +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#----------------------------------------------------------------------- +#some test synonym mappings unlikely to appear in real input text +aaafoo => aaabar +bbbfoo => bbbfoo bbbbar +cccfoo => cccbar cccbaz +fooaaa,baraaa,bazaaa + +# Some synonym groups specific to this example +GB,gib,gigabyte,gigabytes +MB,mib,megabyte,megabytes +Television, Televisions, TV, TVs +#notice we use "gib" instead of "GiB" so any WordDelimiterGraphFilter coming +#after us won't split it into two words. + +# Synonym mappings can be used for spelling correction too +pixima => pixma + diff --git a/dspace/solr/word_highlighting/core.properties b/dspace/solr/word_highlighting/core.properties new file mode 100644 index 0000000000..5ecee74676 --- /dev/null +++ b/dspace/solr/word_highlighting/core.properties @@ -0,0 +1,3 @@ +#Written by CorePropertiesLocator +#Sun Mar 28 20:37:08 UTC 2021 +name=word_highlighting From 031cef8b4578a9bdd80ac9754ef25892dbc11942 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 30 Mar 2021 17:35:46 -0700 Subject: [PATCH 0063/1254] Updated dspace.cfg to support iiif ITs --- dspace/config/dspace.cfg | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 2588a3a3d5..06817efdf8 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1499,20 +1499,20 @@ log.report.dir = ${dspace.dir}/log #### IIIF CONFIGURATION #### # Base URL of the DSpace iiif API endpoint. -#iiif.url = ${dspace.server.url}/api/iiif/ +iiif.url = ${dspace.server.url}/api/iiif/ # Base URL of the IIIF image server. -#iiif.image.server = http://localhost:8182/iiif/2/ +iiif.image.server = http://localhost:8182/iiif/2/ # Base URL of the solr search index (IIIF Search API). -#iiif.solr.search.url = http://localhost:8983/solr/word_highlighting +iiif.solr.search.url = http://localhost:8983/solr/word_highlighting -#iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams +iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams # Sets the viewing hint. Possible values: "paged" or "individuals". # Typically "paged" is preferred for multi-age documents. Use "individuals" # if you plan to implement the search api. -#iiif.document.viewing.hint = individuals +iiif.document.viewing.hint = individuals #---------------------------------------------------------------# #----------------REQUEST ITEM CONFIGURATION---------------------# From 38f68abddbddfaf69a451f62473c9f7c0404ef76 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 30 Mar 2021 17:36:44 -0700 Subject: [PATCH 0064/1254] Minor updates to iiif services. --- .../app/rest/iiif/service/AbstractResourceService.java | 7 ++----- .../dspace/app/rest/iiif/service/ManifestService.java | 9 +++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index a94803bb9f..1d94aaac39 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -7,8 +7,6 @@ */ package org.dspace.app.rest.iiif.service; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.UUID; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; @@ -35,9 +33,8 @@ public abstract class AbstractResourceService { protected String CLIENT_URL; protected String BITSTREAM_PATH_PREFIX; /** - * Possible values: "paged" or "individuals". Typically paged is preferred - * for documents. However, it can be overridden in configuration if necessary - * for the viewer client. + * Possible values: "paged" or "individuals". The property + * value is set in dspace configuration. */ protected static String DOCUMENT_VIEWING_HINT; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index ec63cd750d..671f52073e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -112,7 +112,7 @@ public class ManifestService extends AbstractResourceService { } /** - * Returns a single sequence with canvases and item rendering (optional). + * Returns a single sequence with canvases and a rendering property (optional). * @param item DSpace Item * @param bitstreams list of bitstreams * @param context the DSpace context @@ -121,7 +121,7 @@ public class ManifestService extends AbstractResourceService { private void addSequence(Item item, List bitstreams, Context context, Info info) { sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); if (bitstreams.size() > 0) { - addCanvas(sequenceGenerator, context, item, bitstreams, info); + addCanvases(sequenceGenerator, context, item, bitstreams, info); } addRendering(sequenceGenerator, item, context); manifestGenerator.addSequence(sequenceGenerator); @@ -178,8 +178,9 @@ public class ManifestService extends AbstractResourceService { * @param item the DSpace Item * @param bitstreams list of DSpace bitstreams */ - private void addCanvas(CanvasItemsGenerator sequence, Context context, Item item, - List bitstreams, Info info) { + private void addCanvases(CanvasItemsGenerator sequence, Context context, Item item, + List bitstreams, Info info) { + // TODO: This adds all bitstreams from a bundle. Consider bitstream pagination. /** * Counter tracks the position of the bitstream in the list and is used to create the canvas identifier. * Bitstream order is determined by position in the IIIF DSpace bundle. From 8fd05b1b16166b3bfdb4fedc8a4741b6eaa4afed Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 31 Mar 2021 11:01:07 -0700 Subject: [PATCH 0065/1254] Minor updates. --- .../dspace/app/rest/iiif/service/CanvasLookupService.java | 4 +--- .../org/dspace/app/rest/iiif/service/SearchService.java | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java index 186a3de7cb..4495c5742d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -22,7 +22,7 @@ import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.stereotype.Component; /** - * Canvases may be dereferenced separately from the manifest via their URIs. + * Canvases may be dereferenced separately from the manifest via their IDs. */ @Component public class CanvasLookupService extends AbstractResourceService { @@ -45,8 +45,6 @@ public class CanvasLookupService extends AbstractResourceService { } Info info = utils.validateInfoForSingleCanvas(utils.getInfo(context, item, IIIF_BUNDLE), canvasPosition); - ArrayList bitstreams = new ArrayList<>(); - bitstreams.add(bitstream); UUID bitstreamID = bitstream.getID(); String mimeType = utils.getBitstreamMimeType(bitstream, context); CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, canvasPosition); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index e1d6c6e5e2..95e3cffbf5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -204,10 +204,10 @@ public class SearchService extends AbstractResourceService { private AnnotationGenerator getAnnotation(JsonElement highlight, String pageId, UUID uuid) { JsonObject hcoords = highlight.getAsJsonObject(); String text = (hcoords.get("text").getAsString()); - Integer ulx = hcoords.get("ulx").getAsInt(); - Integer uly = hcoords.get("uly").getAsInt(); - Integer lrx = hcoords.get("lrx").getAsInt(); - Integer lry = hcoords.get("lry").getAsInt(); + int ulx = hcoords.get("ulx").getAsInt(); + int uly = hcoords.get("uly").getAsInt(); + int lrx = hcoords.get("lrx").getAsInt(); + int lry = hcoords.get("lry").getAsInt(); String w = Integer.toString(lrx - ulx); String h = Integer.toString(lry - uly); String params = ulx + "," + uly + "," + w + "," + h; From 2548eef9aecaa1f2a130ad93dd2d3d6eb194a313 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 31 Mar 2021 11:38:15 -0700 Subject: [PATCH 0066/1254] Removed unused import. --- .../org/dspace/app/rest/iiif/service/CanvasLookupService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java index 4495c5742d..51dfbbb08a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.iiif.service; -import java.util.ArrayList; import java.util.UUID; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; From a2929fa17bfbc50588f64ea2ff112904eec9880d Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 31 Mar 2021 18:54:50 -0700 Subject: [PATCH 0067/1254] Removed word_highlighting solr core. --- .../conf/lang/contractions_ca.txt | 8 - .../conf/lang/contractions_fr.txt | 15 - .../conf/lang/contractions_ga.txt | 5 - .../conf/lang/contractions_it.txt | 23 - .../conf/lang/hyphenations_ga.txt | 5 - .../conf/lang/stemdict_nl.txt | 6 - .../conf/lang/stoptags_ja.txt | 420 ----- .../conf/lang/stopwords_ar.txt | 125 -- .../conf/lang/stopwords_bg.txt | 193 -- .../conf/lang/stopwords_ca.txt | 220 --- .../conf/lang/stopwords_cz.txt | 172 -- .../conf/lang/stopwords_da.txt | 110 -- .../conf/lang/stopwords_de.txt | 294 --- .../conf/lang/stopwords_el.txt | 78 - .../conf/lang/stopwords_en.txt | 54 - .../conf/lang/stopwords_es.txt | 356 ---- .../conf/lang/stopwords_et.txt | 1603 ----------------- .../conf/lang/stopwords_eu.txt | 99 - .../conf/lang/stopwords_fa.txt | 313 ---- .../conf/lang/stopwords_fi.txt | 97 - .../conf/lang/stopwords_fr.txt | 186 -- .../conf/lang/stopwords_ga.txt | 110 -- .../conf/lang/stopwords_gl.txt | 161 -- .../conf/lang/stopwords_hi.txt | 235 --- .../conf/lang/stopwords_hu.txt | 211 --- .../conf/lang/stopwords_hy.txt | 46 - .../conf/lang/stopwords_id.txt | 359 ---- .../conf/lang/stopwords_it.txt | 303 ---- .../conf/lang/stopwords_ja.txt | 127 -- .../conf/lang/stopwords_lv.txt | 172 -- .../conf/lang/stopwords_nl.txt | 119 -- .../conf/lang/stopwords_no.txt | 194 -- .../conf/lang/stopwords_pt.txt | 253 --- .../conf/lang/stopwords_ro.txt | 233 --- .../conf/lang/stopwords_ru.txt | 243 --- .../conf/lang/stopwords_sv.txt | 133 -- .../conf/lang/stopwords_th.txt | 119 -- .../conf/lang/stopwords_tr.txt | 212 --- .../conf/lang/userdict_ja.txt | 29 - .../word_highlighting/conf/managed-schema | 543 ------ .../solr/word_highlighting/conf/protwords.txt | 21 - .../word_highlighting/conf/solrconfig.xml | 1313 -------------- .../solr/word_highlighting/conf/stopwords.txt | 14 - .../solr/word_highlighting/conf/synonyms.txt | 29 - dspace/solr/word_highlighting/core.properties | 3 - 45 files changed, 9564 deletions(-) delete mode 100644 dspace/solr/word_highlighting/conf/lang/contractions_ca.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/contractions_fr.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/contractions_ga.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/contractions_it.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/hyphenations_ga.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stemdict_nl.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stoptags_ja.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ar.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_bg.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ca.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_cz.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_da.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_de.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_el.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_en.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_es.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_et.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_eu.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_fa.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_fi.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_fr.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ga.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_gl.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_hi.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_hu.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_hy.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_id.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_it.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ja.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_lv.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_nl.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_no.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_pt.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ro.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_ru.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_sv.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_th.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/stopwords_tr.txt delete mode 100644 dspace/solr/word_highlighting/conf/lang/userdict_ja.txt delete mode 100644 dspace/solr/word_highlighting/conf/managed-schema delete mode 100644 dspace/solr/word_highlighting/conf/protwords.txt delete mode 100644 dspace/solr/word_highlighting/conf/solrconfig.xml delete mode 100644 dspace/solr/word_highlighting/conf/stopwords.txt delete mode 100644 dspace/solr/word_highlighting/conf/synonyms.txt delete mode 100644 dspace/solr/word_highlighting/core.properties diff --git a/dspace/solr/word_highlighting/conf/lang/contractions_ca.txt b/dspace/solr/word_highlighting/conf/lang/contractions_ca.txt deleted file mode 100644 index 307a85f913..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/contractions_ca.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Set of Catalan contractions for ElisionFilter -# TODO: load this as a resource from the analyzer and sync it in build.xml -d -l -m -n -s -t diff --git a/dspace/solr/word_highlighting/conf/lang/contractions_fr.txt b/dspace/solr/word_highlighting/conf/lang/contractions_fr.txt deleted file mode 100644 index f1bba51b23..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/contractions_fr.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Set of French contractions for ElisionFilter -# TODO: load this as a resource from the analyzer and sync it in build.xml -l -m -t -qu -n -s -j -d -c -jusqu -quoiqu -lorsqu -puisqu diff --git a/dspace/solr/word_highlighting/conf/lang/contractions_ga.txt b/dspace/solr/word_highlighting/conf/lang/contractions_ga.txt deleted file mode 100644 index 9ebe7fa349..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/contractions_ga.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Set of Irish contractions for ElisionFilter -# TODO: load this as a resource from the analyzer and sync it in build.xml -d -m -b diff --git a/dspace/solr/word_highlighting/conf/lang/contractions_it.txt b/dspace/solr/word_highlighting/conf/lang/contractions_it.txt deleted file mode 100644 index cac0409537..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/contractions_it.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Set of Italian contractions for ElisionFilter -# TODO: load this as a resource from the analyzer and sync it in build.xml -c -l -all -dall -dell -nell -sull -coll -pell -gl -agl -dagl -degl -negl -sugl -un -m -t -s -v -d diff --git a/dspace/solr/word_highlighting/conf/lang/hyphenations_ga.txt b/dspace/solr/word_highlighting/conf/lang/hyphenations_ga.txt deleted file mode 100644 index 4d2642cc5a..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/hyphenations_ga.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Set of Irish hyphenations for StopFilter -# TODO: load this as a resource from the analyzer and sync it in build.xml -h -n -t diff --git a/dspace/solr/word_highlighting/conf/lang/stemdict_nl.txt b/dspace/solr/word_highlighting/conf/lang/stemdict_nl.txt deleted file mode 100644 index 441072971d..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stemdict_nl.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Set of overrides for the dutch stemmer -# TODO: load this as a resource from the analyzer and sync it in build.xml -fiets fiets -bromfiets bromfiets -ei eier -kind kinder diff --git a/dspace/solr/word_highlighting/conf/lang/stoptags_ja.txt b/dspace/solr/word_highlighting/conf/lang/stoptags_ja.txt deleted file mode 100644 index 71b750845e..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stoptags_ja.txt +++ /dev/null @@ -1,420 +0,0 @@ -# -# This file defines a Japanese stoptag set for JapanesePartOfSpeechStopFilter. -# -# Any token with a part-of-speech tag that exactly matches those defined in this -# file are removed from the token stream. -# -# Set your own stoptags by uncommenting the lines below. Note that comments are -# not allowed on the same line as a stoptag. See LUCENE-3745 for frequency lists, -# etc. that can be useful for building you own stoptag set. -# -# The entire possible tagset is provided below for convenience. -# -##### -# noun: unclassified nouns -#名詞 -# -# noun-common: Common nouns or nouns where the sub-classification is undefined -#名詞-一般 -# -# noun-proper: Proper nouns where the sub-classification is undefined -#名詞-固有名詞 -# -# noun-proper-misc: miscellaneous proper nouns -#名詞-固有名詞-一般 -# -# noun-proper-person: Personal names where the sub-classification is undefined -#名詞-固有名詞-人名 -# -# noun-proper-person-misc: names that cannot be divided into surname and -# given name; foreign names; names where the surname or given name is unknown. -# e.g. お市の方 -#名詞-固有名詞-人名-一般 -# -# noun-proper-person-surname: Mainly Japanese surnames. -# e.g. 山田 -#名詞-固有名詞-人名-姓 -# -# noun-proper-person-given_name: Mainly Japanese given names. -# e.g. 太郎 -#名詞-固有名詞-人名-名 -# -# noun-proper-organization: Names representing organizations. -# e.g. 通産省, NHK -#名詞-固有名詞-組織 -# -# noun-proper-place: Place names where the sub-classification is undefined -#名詞-固有名詞-地域 -# -# noun-proper-place-misc: Place names excluding countries. -# e.g. アジア, バルセロナ, 京都 -#名詞-固有名詞-地域-一般 -# -# noun-proper-place-country: Country names. -# e.g. 日本, オーストラリア -#名詞-固有名詞-地域-国 -# -# noun-pronoun: Pronouns where the sub-classification is undefined -#名詞-代名詞 -# -# noun-pronoun-misc: miscellaneous pronouns: -# e.g. それ, ここ, あいつ, あなた, あちこち, いくつ, どこか, なに, みなさん, みんな, わたくし, われわれ -#名詞-代名詞-一般 -# -# noun-pronoun-contraction: Spoken language contraction made by combining a -# pronoun and the particle 'wa'. -# e.g. ありゃ, こりゃ, こりゃあ, そりゃ, そりゃあ -#名詞-代名詞-縮約 -# -# noun-adverbial: Temporal nouns such as names of days or months that behave -# like adverbs. Nouns that represent amount or ratios and can be used adverbially, -# e.g. 金曜, 一月, 午後, 少量 -#名詞-副詞可能 -# -# noun-verbal: Nouns that take arguments with case and can appear followed by -# 'suru' and related verbs (する, できる, なさる, くださる) -# e.g. インプット, 愛着, 悪化, 悪戦苦闘, 一安心, 下取り -#名詞-サ変接続 -# -# noun-adjective-base: The base form of adjectives, words that appear before な ("na") -# e.g. 健康, 安易, 駄目, だめ -#名詞-形容動詞語幹 -# -# noun-numeric: Arabic numbers, Chinese numerals, and counters like 何 (回), 数. -# e.g. 0, 1, 2, 何, 数, 幾 -#名詞-数 -# -# noun-affix: noun affixes where the sub-classification is undefined -#名詞-非自立 -# -# noun-affix-misc: Of adnominalizers, the case-marker の ("no"), and words that -# attach to the base form of inflectional words, words that cannot be classified -# into any of the other categories below. This category includes indefinite nouns. -# e.g. あかつき, 暁, かい, 甲斐, 気, きらい, 嫌い, くせ, 癖, こと, 事, ごと, 毎, しだい, 次第, -# 順, せい, 所為, ついで, 序で, つもり, 積もり, 点, どころ, の, はず, 筈, はずみ, 弾み, -# 拍子, ふう, ふり, 振り, ほう, 方, 旨, もの, 物, 者, ゆえ, 故, ゆえん, 所以, わけ, 訳, -# わり, 割り, 割, ん-口語/, もん-口語/ -#名詞-非自立-一般 -# -# noun-affix-adverbial: noun affixes that that can behave as adverbs. -# e.g. あいだ, 間, あげく, 挙げ句, あと, 後, 余り, 以外, 以降, 以後, 以上, 以前, 一方, うえ, -# 上, うち, 内, おり, 折り, かぎり, 限り, きり, っきり, 結果, ころ, 頃, さい, 際, 最中, さなか, -# 最中, じたい, 自体, たび, 度, ため, 為, つど, 都度, とおり, 通り, とき, 時, ところ, 所, -# とたん, 途端, なか, 中, のち, 後, ばあい, 場合, 日, ぶん, 分, ほか, 他, まえ, 前, まま, -# 儘, 侭, みぎり, 矢先 -#名詞-非自立-副詞可能 -# -# noun-affix-aux: noun affixes treated as 助動詞 ("auxiliary verb") in school grammars -# with the stem よう(だ) ("you(da)"). -# e.g. よう, やう, 様 (よう) -#名詞-非自立-助動詞語幹 -# -# noun-affix-adjective-base: noun affixes that can connect to the indeclinable -# connection form な (aux "da"). -# e.g. みたい, ふう -#名詞-非自立-形容動詞語幹 -# -# noun-special: special nouns where the sub-classification is undefined. -#名詞-特殊 -# -# noun-special-aux: The そうだ ("souda") stem form that is used for reporting news, is -# treated as 助動詞 ("auxiliary verb") in school grammars, and attach to the base -# form of inflectional words. -# e.g. そう -#名詞-特殊-助動詞語幹 -# -# noun-suffix: noun suffixes where the sub-classification is undefined. -#名詞-接尾 -# -# noun-suffix-misc: Of the nouns or stem forms of other parts of speech that connect -# to ガル or タイ and can combine into compound nouns, words that cannot be classified into -# any of the other categories below. In general, this category is more inclusive than -# 接尾語 ("suffix") and is usually the last element in a compound noun. -# e.g. おき, かた, 方, 甲斐 (がい), がかり, ぎみ, 気味, ぐるみ, (~した) さ, 次第, 済 (ず) み, -# よう, (でき)っこ, 感, 観, 性, 学, 類, 面, 用 -#名詞-接尾-一般 -# -# noun-suffix-person: Suffixes that form nouns and attach to person names more often -# than other nouns. -# e.g. 君, 様, 著 -#名詞-接尾-人名 -# -# noun-suffix-place: Suffixes that form nouns and attach to place names more often -# than other nouns. -# e.g. 町, 市, 県 -#名詞-接尾-地域 -# -# noun-suffix-verbal: Of the suffixes that attach to nouns and form nouns, those that -# can appear before スル ("suru"). -# e.g. 化, 視, 分け, 入り, 落ち, 買い -#名詞-接尾-サ変接続 -# -# noun-suffix-aux: The stem form of そうだ (様態) that is used to indicate conditions, -# is treated as 助動詞 ("auxiliary verb") in school grammars, and attach to the -# conjunctive form of inflectional words. -# e.g. そう -#名詞-接尾-助動詞語幹 -# -# noun-suffix-adjective-base: Suffixes that attach to other nouns or the conjunctive -# form of inflectional words and appear before the copula だ ("da"). -# e.g. 的, げ, がち -#名詞-接尾-形容動詞語幹 -# -# noun-suffix-adverbial: Suffixes that attach to other nouns and can behave as adverbs. -# e.g. 後 (ご), 以後, 以降, 以前, 前後, 中, 末, 上, 時 (じ) -#名詞-接尾-副詞可能 -# -# noun-suffix-classifier: Suffixes that attach to numbers and form nouns. This category -# is more inclusive than 助数詞 ("classifier") and includes common nouns that attach -# to numbers. -# e.g. 個, つ, 本, 冊, パーセント, cm, kg, カ月, か国, 区画, 時間, 時半 -#名詞-接尾-助数詞 -# -# noun-suffix-special: Special suffixes that mainly attach to inflecting words. -# e.g. (楽し) さ, (考え) 方 -#名詞-接尾-特殊 -# -# noun-suffix-conjunctive: Nouns that behave like conjunctions and join two words -# together. -# e.g. (日本) 対 (アメリカ), 対 (アメリカ), (3) 対 (5), (女優) 兼 (主婦) -#名詞-接続詞的 -# -# noun-verbal_aux: Nouns that attach to the conjunctive particle て ("te") and are -# semantically verb-like. -# e.g. ごらん, ご覧, 御覧, 頂戴 -#名詞-動詞非自立的 -# -# noun-quotation: text that cannot be segmented into words, proverbs, Chinese poetry, -# dialects, English, etc. Currently, the only entry for 名詞 引用文字列 ("noun quotation") -# is いわく ("iwaku"). -#名詞-引用文字列 -# -# noun-nai_adjective: Words that appear before the auxiliary verb ない ("nai") and -# behave like an adjective. -# e.g. 申し訳, 仕方, とんでも, 違い -#名詞-ナイ形容詞語幹 -# -##### -# prefix: unclassified prefixes -#接頭詞 -# -# prefix-nominal: Prefixes that attach to nouns (including adjective stem forms) -# excluding numerical expressions. -# e.g. お (水), 某 (氏), 同 (社), 故 (~氏), 高 (品質), お (見事), ご (立派) -#接頭詞-名詞接続 -# -# prefix-verbal: Prefixes that attach to the imperative form of a verb or a verb -# in conjunctive form followed by なる/なさる/くださる. -# e.g. お (読みなさい), お (座り) -#接頭詞-動詞接続 -# -# prefix-adjectival: Prefixes that attach to adjectives. -# e.g. お (寒いですねえ), バカ (でかい) -#接頭詞-形容詞接続 -# -# prefix-numerical: Prefixes that attach to numerical expressions. -# e.g. 約, およそ, 毎時 -#接頭詞-数接続 -# -##### -# verb: unclassified verbs -#動詞 -# -# verb-main: -#動詞-自立 -# -# verb-auxiliary: -#動詞-非自立 -# -# verb-suffix: -#動詞-接尾 -# -##### -# adjective: unclassified adjectives -#形容詞 -# -# adjective-main: -#形容詞-自立 -# -# adjective-auxiliary: -#形容詞-非自立 -# -# adjective-suffix: -#形容詞-接尾 -# -##### -# adverb: unclassified adverbs -#副詞 -# -# adverb-misc: Words that can be segmented into one unit and where adnominal -# modification is not possible. -# e.g. あいかわらず, 多分 -#副詞-一般 -# -# adverb-particle_conjunction: Adverbs that can be followed by の, は, に, -# な, する, だ, etc. -# e.g. こんなに, そんなに, あんなに, なにか, なんでも -#副詞-助詞類接続 -# -##### -# adnominal: Words that only have noun-modifying forms. -# e.g. この, その, あの, どの, いわゆる, なんらかの, 何らかの, いろんな, こういう, そういう, ああいう, -# どういう, こんな, そんな, あんな, どんな, 大きな, 小さな, おかしな, ほんの, たいした, -# 「(, も) さる (ことながら)」, 微々たる, 堂々たる, 単なる, いかなる, 我が」「同じ, 亡き -#連体詞 -# -##### -# conjunction: Conjunctions that can occur independently. -# e.g. が, けれども, そして, じゃあ, それどころか -接続詞 -# -##### -# particle: unclassified particles. -助詞 -# -# particle-case: case particles where the subclassification is undefined. -助詞-格助詞 -# -# particle-case-misc: Case particles. -# e.g. から, が, で, と, に, へ, より, を, の, にて -助詞-格助詞-一般 -# -# particle-case-quote: the "to" that appears after nouns, a person’s speech, -# quotation marks, expressions of decisions from a meeting, reasons, judgements, -# conjectures, etc. -# e.g. ( だ) と (述べた.), ( である) と (して執行猶予...) -助詞-格助詞-引用 -# -# particle-case-compound: Compounds of particles and verbs that mainly behave -# like case particles. -# e.g. という, といった, とかいう, として, とともに, と共に, でもって, にあたって, に当たって, に当って, -# にあたり, に当たり, に当り, に当たる, にあたる, において, に於いて,に於て, における, に於ける, -# にかけ, にかけて, にかんし, に関し, にかんして, に関して, にかんする, に関する, に際し, -# に際して, にしたがい, に従い, に従う, にしたがって, に従って, にたいし, に対し, にたいして, -# に対して, にたいする, に対する, について, につき, につけ, につけて, につれ, につれて, にとって, -# にとり, にまつわる, によって, に依って, に因って, により, に依り, に因り, による, に依る, に因る, -# にわたって, にわたる, をもって, を以って, を通じ, を通じて, を通して, をめぐって, をめぐり, をめぐる, -# って-口語/, ちゅう-関西弁「という」/, (何) ていう (人)-口語/, っていう-口語/, といふ, とかいふ -助詞-格助詞-連語 -# -# particle-conjunctive: -# e.g. から, からには, が, けれど, けれども, けど, し, つつ, て, で, と, ところが, どころか, とも, ども, -# ながら, なり, ので, のに, ば, ものの, や ( した), やいなや, (ころん) じゃ(いけない)-口語/, -# (行っ) ちゃ(いけない)-口語/, (言っ) たって (しかたがない)-口語/, (それがなく)ったって (平気)-口語/ -助詞-接続助詞 -# -# particle-dependency: -# e.g. こそ, さえ, しか, すら, は, も, ぞ -助詞-係助詞 -# -# particle-adverbial: -# e.g. がてら, かも, くらい, 位, ぐらい, しも, (学校) じゃ(これが流行っている)-口語/, -# (それ)じゃあ (よくない)-口語/, ずつ, (私) なぞ, など, (私) なり (に), (先生) なんか (大嫌い)-口語/, -# (私) なんぞ, (先生) なんて (大嫌い)-口語/, のみ, だけ, (私) だって-口語/, だに, -# (彼)ったら-口語/, (お茶) でも (いかが), 等 (とう), (今後) とも, ばかり, ばっか-口語/, ばっかり-口語/, -# ほど, 程, まで, 迄, (誰) も (が)([助詞-格助詞] および [助詞-係助詞] の前に位置する「も」) -助詞-副助詞 -# -# particle-interjective: particles with interjective grammatical roles. -# e.g. (松島) や -助詞-間投助詞 -# -# particle-coordinate: -# e.g. と, たり, だの, だり, とか, なり, や, やら -助詞-並立助詞 -# -# particle-final: -# e.g. かい, かしら, さ, ぜ, (だ)っけ-口語/, (とまってる) で-方言/, な, ナ, なあ-口語/, ぞ, ね, ネ, -# ねぇ-口語/, ねえ-口語/, ねん-方言/, の, のう-口語/, や, よ, ヨ, よぉ-口語/, わ, わい-口語/ -助詞-終助詞 -# -# particle-adverbial/conjunctive/final: The particle "ka" when unknown whether it is -# adverbial, conjunctive, or sentence final. For example: -# (a) 「A か B か」. Ex:「(国内で運用する) か,(海外で運用する) か (.)」 -# (b) Inside an adverb phrase. Ex:「(幸いという) か (, 死者はいなかった.)」 -# 「(祈りが届いたせい) か (, 試験に合格した.)」 -# (c) 「かのように」. Ex:「(何もなかった) か (のように振る舞った.)」 -# e.g. か -助詞-副助詞/並立助詞/終助詞 -# -# particle-adnominalizer: The "no" that attaches to nouns and modifies -# non-inflectional words. -助詞-連体化 -# -# particle-adnominalizer: The "ni" and "to" that appear following nouns and adverbs -# that are giongo, giseigo, or gitaigo. -# e.g. に, と -助詞-副詞化 -# -# particle-special: A particle that does not fit into one of the above classifications. -# This includes particles that are used in Tanka, Haiku, and other poetry. -# e.g. かな, けむ, ( しただろう) に, (あんた) にゃ(わからん), (俺) ん (家) -助詞-特殊 -# -##### -# auxiliary-verb: -助動詞 -# -##### -# interjection: Greetings and other exclamations. -# e.g. おはよう, おはようございます, こんにちは, こんばんは, ありがとう, どうもありがとう, ありがとうございます, -# いただきます, ごちそうさま, さよなら, さようなら, はい, いいえ, ごめん, ごめんなさい -#感動詞 -# -##### -# symbol: unclassified Symbols. -記号 -# -# symbol-misc: A general symbol not in one of the categories below. -# e.g. [○◎@$〒→+] -記号-一般 -# -# symbol-comma: Commas -# e.g. [,、] -記号-読点 -# -# symbol-period: Periods and full stops. -# e.g. [..。] -記号-句点 -# -# symbol-space: Full-width whitespace. -記号-空白 -# -# symbol-open_bracket: -# e.g. [({‘“『【] -記号-括弧開 -# -# symbol-close_bracket: -# e.g. [)}’”』」】] -記号-括弧閉 -# -# symbol-alphabetic: -#記号-アルファベット -# -##### -# other: unclassified other -#その他 -# -# other-interjection: Words that are hard to classify as noun-suffixes or -# sentence-final particles. -# e.g. (だ)ァ -その他-間投 -# -##### -# filler: Aizuchi that occurs during a conversation or sounds inserted as filler. -# e.g. あの, うんと, えと -フィラー -# -##### -# non-verbal: non-verbal sound. -非言語音 -# -##### -# fragment: -#語断片 -# -##### -# unknown: unknown part of speech. -#未知語 -# -##### End of file diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ar.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ar.txt deleted file mode 100644 index 046829db6a..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_ar.txt +++ /dev/null @@ -1,125 +0,0 @@ -# This file was created by Jacques Savoy and is distributed under the BSD license. -# See http://members.unine.ch/jacques.savoy/clef/index.html. -# Also see http://www.opensource.org/licenses/bsd-license.html -# Cleaned on October 11, 2009 (not normalized, so use before normalization) -# This means that when modifying this list, you might need to add some -# redundant entries, for example containing forms with both أ and ا -من -ومن -منها -منه -في -وفي -فيها -فيه -و -ف -ثم -او -أو -ب -بها -به -ا -أ -اى -اي -أي -أى -لا -ولا -الا -ألا -إلا -لكن -ما -وما -كما -فما -عن -مع -اذا -إذا -ان -أن -إن -انها -أنها -إنها -انه -أنه -إنه -بان -بأن -فان -فأن -وان -وأن -وإن -التى -التي -الذى -الذي -الذين -الى -الي -إلى -إلي -على -عليها -عليه -اما -أما -إما -ايضا -أيضا -كل -وكل -لم -ولم -لن -ولن -هى -هي -هو -وهى -وهي -وهو -فهى -فهي -فهو -انت -أنت -لك -لها -له -هذه -هذا -تلك -ذلك -هناك -كانت -كان -يكون -تكون -وكانت -وكان -غير -بعض -قد -نحو -بين -بينما -منذ -ضمن -حيث -الان -الآن -خلال -بعد -قبل -حتى -عند -عندما -لدى -جميع diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_bg.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_bg.txt deleted file mode 100644 index 1ae4ba2ae3..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_bg.txt +++ /dev/null @@ -1,193 +0,0 @@ -# This file was created by Jacques Savoy and is distributed under the BSD license. -# See http://members.unine.ch/jacques.savoy/clef/index.html. -# Also see http://www.opensource.org/licenses/bsd-license.html -а -аз -ако -ала -бе -без -беше -би -бил -била -били -било -близо -бъдат -бъде -бяха -в -вас -ваш -ваша -вероятно -вече -взема -ви -вие -винаги -все -всеки -всички -всичко -всяка -във -въпреки -върху -г -ги -главно -го -д -да -дали -до -докато -докога -дори -досега -доста -е -едва -един -ето -за -зад -заедно -заради -засега -затова -защо -защото -и -из -или -им -има -имат -иска -й -каза -как -каква -какво -както -какъв -като -кога -когато -което -които -кой -който -колко -която -къде -където -към -ли -м -ме -между -мен -ми -мнозина -мога -могат -може -моля -момента -му -н -на -над -назад -най -направи -напред -например -нас -не -него -нея -ни -ние -никой -нито -но -някои -някой -няма -обаче -около -освен -особено -от -отгоре -отново -още -пак -по -повече -повечето -под -поне -поради -после -почти -прави -пред -преди -през -при -пък -първо -с -са -само -се -сега -си -скоро -след -сме -според -сред -срещу -сте -съм -със -също -т -тази -така -такива -такъв -там -твой -те -тези -ти -тн -то -това -тогава -този -той -толкова -точно -трябва -тук -тъй -тя -тях -у -харесва -ч -че -често -чрез -ще -щом -я diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ca.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ca.txt deleted file mode 100644 index 3da65deafe..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_ca.txt +++ /dev/null @@ -1,220 +0,0 @@ -# Catalan stopwords from http://github.com/vcl/cue.language (Apache 2 Licensed) -a -abans -ací -ah -així -això -al -als -aleshores -algun -alguna -algunes -alguns -alhora -allà -allí -allò -altra -altre -altres -amb -ambdós -ambdues -apa -aquell -aquella -aquelles -aquells -aquest -aquesta -aquestes -aquests -aquí -baix -cada -cadascú -cadascuna -cadascunes -cadascuns -com -contra -d'un -d'una -d'unes -d'uns -dalt -de -del -dels -des -després -dins -dintre -donat -doncs -durant -e -eh -el -els -em -en -encara -ens -entre -érem -eren -éreu -es -és -esta -està -estàvem -estaven -estàveu -esteu -et -etc -ets -fins -fora -gairebé -ha -han -has -havia -he -hem -heu -hi -ho -i -igual -iguals -ja -l'hi -la -les -li -li'n -llavors -m'he -ma -mal -malgrat -mateix -mateixa -mateixes -mateixos -me -mentre -més -meu -meus -meva -meves -molt -molta -moltes -molts -mon -mons -n'he -n'hi -ne -ni -no -nogensmenys -només -nosaltres -nostra -nostre -nostres -o -oh -oi -on -pas -pel -pels -per -però -perquè -poc -poca -pocs -poques -potser -propi -qual -quals -quan -quant -que -què -quelcom -qui -quin -quina -quines -quins -s'ha -s'han -sa -semblant -semblants -ses -seu -seus -seva -seva -seves -si -sobre -sobretot -sóc -solament -sols -son -són -sons -sota -sou -t'ha -t'han -t'he -ta -tal -també -tampoc -tan -tant -tanta -tantes -teu -teus -teva -teves -ton -tons -tot -tota -totes -tots -un -una -unes -uns -us -va -vaig -vam -van -vas -veu -vosaltres -vostra -vostre -vostres diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_cz.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_cz.txt deleted file mode 100644 index 53c6097dac..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_cz.txt +++ /dev/null @@ -1,172 +0,0 @@ -a -s -k -o -i -u -v -z -dnes -cz -tímto -budeš -budem -byli -jseš -můj -svým -ta -tomto -tohle -tuto -tyto -jej -zda -proč -máte -tato -kam -tohoto -kdo -kteří -mi -nám -tom -tomuto -mít -nic -proto -kterou -byla -toho -protože -asi -ho -naši -napište -re -což -tím -takže -svých -její -svými -jste -aj -tu -tedy -teto -bylo -kde -ke -pravé -ji -nad -nejsou -či -pod -téma -mezi -přes -ty -pak -vám -ani -když -však -neg -jsem -tento -článku -články -aby -jsme -před -pta -jejich -byl -ještě -až -bez -také -pouze -první -vaše -která -nás -nový -tipy -pokud -může -strana -jeho -své -jiné -zprávy -nové -není -vás -jen -podle -zde -už -být -více -bude -již -než -který -by -které -co -nebo -ten -tak -má -při -od -po -jsou -jak -další -ale -si -se -ve -to -jako -za -zpět -ze -do -pro -je -na -atd -atp -jakmile -přičemž -já -on -ona -ono -oni -ony -my -vy -jí -ji -mě -mne -jemu -tomu -těm -těmu -němu -němuž -jehož -jíž -jelikož -jež -jakož -načež diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_da.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_da.txt deleted file mode 100644 index 42e6145b98..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_da.txt +++ /dev/null @@ -1,110 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/danish/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | A Danish stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - - | This is a ranked list (commonest to rarest) of stopwords derived from - | a large text sample. - - -og | and -i | in -jeg | I -det | that (dem. pronoun)/it (pers. pronoun) -at | that (in front of a sentence)/to (with infinitive) -en | a/an -den | it (pers. pronoun)/that (dem. pronoun) -til | to/at/for/until/against/by/of/into, more -er | present tense of "to be" -som | who, as -på | on/upon/in/on/at/to/after/of/with/for, on -de | they -med | with/by/in, along -han | he -af | of/by/from/off/for/in/with/on, off -for | at/for/to/from/by/of/ago, in front/before, because -ikke | not -der | who/which, there/those -var | past tense of "to be" -mig | me/myself -sig | oneself/himself/herself/itself/themselves -men | but -et | a/an/one, one (number), someone/somebody/one -har | present tense of "to have" -om | round/about/for/in/a, about/around/down, if -vi | we -min | my -havde | past tense of "to have" -ham | him -hun | she -nu | now -over | over/above/across/by/beyond/past/on/about, over/past -da | then, when/as/since -fra | from/off/since, off, since -du | you -ud | out -sin | his/her/its/one's -dem | them -os | us/ourselves -op | up -man | you/one -hans | his -hvor | where -eller | or -hvad | what -skal | must/shall etc. -selv | myself/youself/herself/ourselves etc., even -her | here -alle | all/everyone/everybody etc. -vil | will (verb) -blev | past tense of "to stay/to remain/to get/to become" -kunne | could -ind | in -når | when -være | present tense of "to be" -dog | however/yet/after all -noget | something -ville | would -jo | you know/you see (adv), yes -deres | their/theirs -efter | after/behind/according to/for/by/from, later/afterwards -ned | down -skulle | should -denne | this -end | than -dette | this -mit | my/mine -også | also -under | under/beneath/below/during, below/underneath -have | have -dig | you -anden | other -hende | her -mine | my -alt | everything -meget | much/very, plenty of -sit | his, her, its, one's -sine | his, her, its, one's -vor | our -mod | against -disse | these -hvis | if -din | your/yours -nogle | some -hos | by/at -blive | be/become -mange | many -ad | by/through -bliver | present tense of "to be/to become" -hendes | her/hers -været | be -thi | for (conj) -jer | you -sådan | such, like this/like that diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_de.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_de.txt deleted file mode 100644 index 86525e7ae0..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_de.txt +++ /dev/null @@ -1,294 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/german/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | A German stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - - | The number of forms in this list is reduced significantly by passing it - | through the German stemmer. - - -aber | but - -alle | all -allem -allen -aller -alles - -als | than, as -also | so -am | an + dem -an | at - -ander | other -andere -anderem -anderen -anderer -anderes -anderm -andern -anderr -anders - -auch | also -auf | on -aus | out of -bei | by -bin | am -bis | until -bist | art -da | there -damit | with it -dann | then - -der | the -den -des -dem -die -das - -daß | that - -derselbe | the same -derselben -denselben -desselben -demselben -dieselbe -dieselben -dasselbe - -dazu | to that - -dein | thy -deine -deinem -deinen -deiner -deines - -denn | because - -derer | of those -dessen | of him - -dich | thee -dir | to thee -du | thou - -dies | this -diese -diesem -diesen -dieser -dieses - - -doch | (several meanings) -dort | (over) there - - -durch | through - -ein | a -eine -einem -einen -einer -eines - -einig | some -einige -einigem -einigen -einiger -einiges - -einmal | once - -er | he -ihn | him -ihm | to him - -es | it -etwas | something - -euer | your -eure -eurem -euren -eurer -eures - -für | for -gegen | towards -gewesen | p.p. of sein -hab | have -habe | have -haben | have -hat | has -hatte | had -hatten | had -hier | here -hin | there -hinter | behind - -ich | I -mich | me -mir | to me - - -ihr | you, to her -ihre -ihrem -ihren -ihrer -ihres -euch | to you - -im | in + dem -in | in -indem | while -ins | in + das -ist | is - -jede | each, every -jedem -jeden -jeder -jedes - -jene | that -jenem -jenen -jener -jenes - -jetzt | now -kann | can - -kein | no -keine -keinem -keinen -keiner -keines - -können | can -könnte | could -machen | do -man | one - -manche | some, many a -manchem -manchen -mancher -manches - -mein | my -meine -meinem -meinen -meiner -meines - -mit | with -muss | must -musste | had to -nach | to(wards) -nicht | not -nichts | nothing -noch | still, yet -nun | now -nur | only -ob | whether -oder | or -ohne | without -sehr | very - -sein | his -seine -seinem -seinen -seiner -seines - -selbst | self -sich | herself - -sie | they, she -ihnen | to them - -sind | are -so | so - -solche | such -solchem -solchen -solcher -solches - -soll | shall -sollte | should -sondern | but -sonst | else -über | over -um | about, around -und | and - -uns | us -unse -unsem -unsen -unser -unses - -unter | under -viel | much -vom | von + dem -von | from -vor | before -während | while -war | was -waren | were -warst | wast -was | what -weg | away, off -weil | because -weiter | further - -welche | which -welchem -welchen -welcher -welches - -wenn | when -werde | will -werden | will -wie | how -wieder | again -will | want -wir | we -wird | will -wirst | willst -wo | where -wollen | want -wollte | wanted -würde | would -würden | would -zu | to -zum | zu + dem -zur | zu + der -zwar | indeed -zwischen | between - diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_el.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_el.txt deleted file mode 100644 index 232681f5bd..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_el.txt +++ /dev/null @@ -1,78 +0,0 @@ -# Lucene Greek Stopwords list -# Note: by default this file is used after GreekLowerCaseFilter, -# so when modifying this file use 'σ' instead of 'ς' -ο -η -το -οι -τα -του -τησ -των -τον -την -και -κι -κ -ειμαι -εισαι -ειναι -ειμαστε -ειστε -στο -στον -στη -στην -μα -αλλα -απο -για -προσ -με -σε -ωσ -παρα -αντι -κατα -μετα -θα -να -δε -δεν -μη -μην -επι -ενω -εαν -αν -τοτε -που -πωσ -ποιοσ -ποια -ποιο -ποιοι -ποιεσ -ποιων -ποιουσ -αυτοσ -αυτη -αυτο -αυτοι -αυτων -αυτουσ -αυτεσ -αυτα -εκεινοσ -εκεινη -εκεινο -εκεινοι -εκεινεσ -εκεινα -εκεινων -εκεινουσ -οπωσ -ομωσ -ισωσ -οσο -οτι diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_en.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_en.txt deleted file mode 100644 index 2c164c0b2a..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_en.txt +++ /dev/null @@ -1,54 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# a couple of test stopwords to test that the words are really being -# configured from this file: -stopworda -stopwordb - -# Standard english stop words taken from Lucene's StopAnalyzer -a -an -and -are -as -at -be -but -by -for -if -in -into -is -it -no -not -of -on -or -such -that -the -their -then -there -these -they -this -to -was -will -with diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_es.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_es.txt deleted file mode 100644 index 487d78c8d5..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_es.txt +++ /dev/null @@ -1,356 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/spanish/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | A Spanish stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - - - | The following is a ranked list (commonest to rarest) of stopwords - | deriving from a large sample of text. - - | Extra words have been added at the end. - -de | from, of -la | the, her -que | who, that -el | the -en | in -y | and -a | to -los | the, them -del | de + el -se | himself, from him etc -las | the, them -por | for, by, etc -un | a -para | for -con | with -no | no -una | a -su | his, her -al | a + el - | es from SER -lo | him -como | how -más | more -pero | pero -sus | su plural -le | to him, her -ya | already -o | or - | fue from SER -este | this - | ha from HABER -sí | himself etc -porque | because -esta | this - | son from SER -entre | between - | está from ESTAR -cuando | when -muy | very -sin | without -sobre | on - | ser from SER - | tiene from TENER -también | also -me | me -hasta | until -hay | there is/are -donde | where - | han from HABER -quien | whom, that - | están from ESTAR - | estado from ESTAR -desde | from -todo | all -nos | us -durante | during - | estados from ESTAR -todos | all -uno | a -les | to them -ni | nor -contra | against -otros | other - | fueron from SER -ese | that -eso | that - | había from HABER -ante | before -ellos | they -e | and (variant of y) -esto | this -mí | me -antes | before -algunos | some -qué | what? -unos | a -yo | I -otro | other -otras | other -otra | other -él | he -tanto | so much, many -esa | that -estos | these -mucho | much, many -quienes | who -nada | nothing -muchos | many -cual | who - | sea from SER -poco | few -ella | she -estar | to be - | haber from HABER -estas | these - | estaba from ESTAR - | estamos from ESTAR -algunas | some -algo | something -nosotros | we - - | other forms - -mi | me -mis | mi plural -tú | thou -te | thee -ti | thee -tu | thy -tus | tu plural -ellas | they -nosotras | we -vosotros | you -vosotras | you -os | you -mío | mine -mía | -míos | -mías | -tuyo | thine -tuya | -tuyos | -tuyas | -suyo | his, hers, theirs -suya | -suyos | -suyas | -nuestro | ours -nuestra | -nuestros | -nuestras | -vuestro | yours -vuestra | -vuestros | -vuestras | -esos | those -esas | those - - | forms of estar, to be (not including the infinitive): -estoy -estás -está -estamos -estáis -están -esté -estés -estemos -estéis -estén -estaré -estarás -estará -estaremos -estaréis -estarán -estaría -estarías -estaríamos -estaríais -estarían -estaba -estabas -estábamos -estabais -estaban -estuve -estuviste -estuvo -estuvimos -estuvisteis -estuvieron -estuviera -estuvieras -estuviéramos -estuvierais -estuvieran -estuviese -estuvieses -estuviésemos -estuvieseis -estuviesen -estando -estado -estada -estados -estadas -estad - - | forms of haber, to have (not including the infinitive): -he -has -ha -hemos -habéis -han -haya -hayas -hayamos -hayáis -hayan -habré -habrás -habrá -habremos -habréis -habrán -habría -habrías -habríamos -habríais -habrían -había -habías -habíamos -habíais -habían -hube -hubiste -hubo -hubimos -hubisteis -hubieron -hubiera -hubieras -hubiéramos -hubierais -hubieran -hubiese -hubieses -hubiésemos -hubieseis -hubiesen -habiendo -habido -habida -habidos -habidas - - | forms of ser, to be (not including the infinitive): -soy -eres -es -somos -sois -son -sea -seas -seamos -seáis -sean -seré -serás -será -seremos -seréis -serán -sería -serías -seríamos -seríais -serían -era -eras -éramos -erais -eran -fui -fuiste -fue -fuimos -fuisteis -fueron -fuera -fueras -fuéramos -fuerais -fueran -fuese -fueses -fuésemos -fueseis -fuesen -siendo -sido - | sed also means 'thirst' - - | forms of tener, to have (not including the infinitive): -tengo -tienes -tiene -tenemos -tenéis -tienen -tenga -tengas -tengamos -tengáis -tengan -tendré -tendrás -tendrá -tendremos -tendréis -tendrán -tendría -tendrías -tendríamos -tendríais -tendrían -tenía -tenías -teníamos -teníais -tenían -tuve -tuviste -tuvo -tuvimos -tuvisteis -tuvieron -tuviera -tuvieras -tuviéramos -tuvierais -tuvieran -tuviese -tuvieses -tuviésemos -tuvieseis -tuviesen -teniendo -tenido -tenida -tenidos -tenidas -tened - diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_et.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_et.txt deleted file mode 100644 index 1b06a134b9..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_et.txt +++ /dev/null @@ -1,1603 +0,0 @@ -# Estonian stopwords list -all -alla -allapoole -allpool -alt -altpoolt -eel -eespool -enne -hommikupoole -hoolimata -ilma -kaudu -keset -kesk -kohe -koos -kuhupoole -kuni -kuspool -kustpoolt -kõige -käsikäes -lappi -ligi -läbi -mööda -paitsi -peale -pealepoole -pealpool -pealt -pealtpoolt -piki -pikku -piku -pikuti -põiki -pärast -päri -risti -sealpool -sealtpoolt -seespool -seltsis -siiapoole -siinpool -siitpoolt -sinnapoole -sissepoole -taga -tagantpoolt -tagapidi -tagapool -taha -tahapoole -teispool -teispoole -tänu -tükkis -vaatamata -vastu -väljapoole -väljaspool -väljastpoolt -õhtupoole -ühes -ühestükis -ühestükkis -ülalpool -ülaltpoolt -üle -ülespoole -ülevalpool -ülevaltpoolt -ümber -ümbert -aegu -aegus -alguks -algul -algule -algult -alguni -all -alla -alt -alul -alutsi -arvel -asemel -asemele -eel -eeli -ees -eesotsas -eest -eestotsast -esitsi -ette -etteotsa -haaval -heaks -hoolimata -hulgas -hulgast -hulka -jalgu -jalus -jalust -jaoks -jooksul -juurde -juures -juurest -jälil -jälile -järel -järele -järelt -järgi -kaasas -kallal -kallale -kallalt -kamul -kannul -kannule -kannult -kaudu -kaupa -keskel -keskele -keskelt -keskis -keskpaiku -kestel -kestes -kilda -killas -killast -kimpu -kimpus -kiuste -kohal -kohale -kohalt -kohaselt -kohe -kohta -koos -korral -kukil -kukile -kukilt -kulul -kõrva -kõrval -kõrvale -kõrvalt -kõrvas -kõrvast -käekõrval -käekõrvale -käekõrvalt -käes -käest -kätte -külge -küljes -küljest -küüsi -küüsis -küüsist -ligi -ligidal -ligidale -ligidalt -aegu -aegus -alguks -algul -algule -algult -alguni -all -alla -alt -alul -alutsi -arvel -asemel -asemele -eel -eeli -ees -eesotsas -eest -eestotsast -esitsi -ette -etteotsa -haaval -heaks -hoolimata -hulgas -hulgast -hulka -jalgu -jalus -jalust -jaoks -jooksul -juurde -juures -juurest -jälil -jälile -järel -järele -järelt -järgi -kaasas -kallal -kallale -kallalt -kamul -kannul -kannule -kannult -kaudu -kaupa -keskel -keskele -keskelt -keskis -keskpaiku -kestel -kestes -kilda -killas -killast -kimpu -kimpus -kiuste -kohal -kohale -kohalt -kohaselt -kohe -kohta -koos -korral -kukil -kukile -kukilt -kulul -kõrva -kõrval -kõrvale -kõrvalt -kõrvas -kõrvast -käekõrval -käekõrvale -käekõrvalt -käes -käest -kätte -külge -küljes -küljest -küüsi -küüsis -küüsist -ligi -ligidal -ligidale -ligidalt -lool -läbi -lähedal -lähedale -lähedalt -man -mant -manu -meelest -mööda -nahas -nahka -nahkas -najal -najale -najalt -nõjal -nõjale -otsa -otsas -otsast -paigale -paigu -paiku -peal -peale -pealt -perra -perrä -pidi -pihta -piki -pikku -pool -poole -poolest -poolt -puhul -puksiiris -pähe -päralt -päras -pärast -päri -ringi -ringis -risust -saadetusel -saadik -saatel -saati -seas -seast -sees -seest -sekka -seljataga -seltsi -seltsis -seltsist -sisse -slepis -suhtes -šlepis -taga -tagant -tagantotsast -tagaotsas -tagaselja -tagasi -tagast -tagutsi -taha -tahaotsa -takka -tarvis -tasa -tuuri -tuuris -tõttu -tükkis -uhal -vaatamata -vahel -vahele -vahelt -vahepeal -vahepeale -vahepealt -vahetsi -varal -varale -varul -vastas -vastast -vastu -veerde -veeres -viisi -võidu -võrd -võrdki -võrra -võrragi -väel -väele -vältel -väärt -väärtki -äärde -ääre -ääres -äärest -ühes -üle -ümber -ümbert -a -abil -aina -ainult -alalt -alates -alati -alles -b -c -d -e -eales -ealeski -edasi -edaspidi -eelkõige -eemal -ei -eks -end -enda -enese -ennem -esialgu -f -g -h -hoopis -i -iganes -igatahes -igati -iial -iialgi -ikka -ikkagi -ilmaski -iseenda -iseenese -iseenesest -isegi -j -jah -ju -juba -juhul -just -järelikult -k -ka -kah -kas -kasvõi -keda -kestahes -kogu -koguni -kohati -kokku -kuhu -kuhugi -kuidagi -kuidas -kunagi -kus -kusagil -kusjuures -kuskil -kust -kõigepealt -küll -l -liiga -lisaks -m -miks -mil -millal -millalgi -mispärast -mistahes -mistõttu -mitte -muide -muidu -muidugi -muist -mujal -mujale -mujalt -mõlemad -mõnda -mõne -mõnikord -n -nii -niikaua -niimoodi -niipaljuke -niisama -niisiis -niivõrd -nõnda -nüüd -o -omaette -omakorda -omavahel -ometi -p -palju -paljuke -palju-palju -peaaegu -peagi -peamiselt -pigem -pisut -praegu -päris -r -rohkem -s -samas -samuti -seal -sealt -sedakorda -sedapuhku -seega -seejuures -seejärel -seekord -seepärast -seetõttu -sellepärast -seni -sestap -siia -siiani -siin -siinkohal -siis -siiski -siit -sinna -suht -š -z -ž -t -teel -teineteise -tõesti -täiesti -u -umbes -v -w -veel -veelgi -vist -võibolla -võib-olla -väga -vähemalt -välja -väljas -väljast -õ -ä -ära -ö -ü -ühtlasi -üksi -ükskõik -ülal -ülale -ülalt -üles -ülesse -üleval -ülevalt -ülimalt -üsna -x -y -aga -ega -ehk -ehkki -elik -ellik -enge -ennegu -ent -et -ja -justkui -kui -kuid -kuigi -kuivõrd -kuna -kuni -kut -mistab -muudkui -nagu -nigu -ning -olgugi -otsekui -otsenagu -selmet -sest -sestab -vaid -või -aa -adaa -adjöö -ae -ah -ahaa -ahah -ah-ah-ah -ah-haa -ahoi -ai -aidaa -aidu-raidu -aih -aijeh -aituma -aitäh -aitüma -ammuu -amps -ampsti -aptsih -ass -at -ata -at-at-at -atsih -atsihh -auh -bai-bai -bingo -braavo -brr -ee -eeh -eh -ehee -eheh -eh-eh-hee -eh-eh-ee -ehei -ehh -ehhee -einoh -ena -ennäe -ennäh -fuh -fui -fuih -haa -hah -hahaa -hah-hah-hah -halleluuja -hallo -halloo -hass -hee -heh -he-he-hee -hei -heldeke(ne) -heureka -hihii -hip-hip-hurraa -hmh -hmjah -hoh-hoh-hoo -hohoo -hoi -hollallaa -hoo -hoplaa -hopp -hops -hopsassaa -hopsti -hosianna -huh -huidii -huist -hurjah -hurjeh -hurjoh -hurjuh -hurraa -huu -hõhõh -hõi -hõissa -hõissassa -hõk -hõkk -häh -hä-hä-hää -hüvasti -ih-ah-haa -ih-ih-hii -ii-ha-ha -issake -issakene -isver -jaa-ah -ja-ah -jaah -janäe -jeeh -jeerum -jeever -jessas -jestas -juhhei -jumalaga -jumalime -jumaluke -jumalukene -jutas -kaaps -kaapsti -kaasike -kae -kalps -kalpsti -kannäe -kanäe -kappadi -kaps -kapsti -karkõmm -karkäuh -karkääks -karkääksti -karmauh -karmauhti -karnaps -karnapsti -karniuhti -karpartsaki -karpauh -karpauhti -karplauh -karplauhti -karprauh -karprauhti -karsumdi -karsumm -kartsumdi -kartsumm -karviuh -karviuhti -kaske -kassa -kauh -kauhti -keh -keksti -kepsti -khe -khm -kih -kiiks -kiiksti -kiis -kiiss -kikerii -kikerikii -kili -kilk -kilk-kõlk -kilks -kilks-kolks -kilks-kõlks -kill -killadi -killadi|-kolladi -killadi-kõlladi -killa-kolla -killa-kõlla -kill-kõll -kimps-komps -kipp -kips-kõps -kiriküüt -kirra-kõrra -kirr-kõrr -kirts -klaps -klapsti -klirdi -klirr -klonks -klops -klopsti -kluk -klu-kluu -klõks -klõksti -klõmdi -klõmm -klõmpsti -klõnks -klõnksti -klõps -klõpsti -kläu -kohva-kohva -kok -koks -koksti -kolaki -kolk -kolks -kolksti -koll -kolladi -komp -komps -kompsti -kop -kopp -koppadi -kops -kopsti -kossu -kotsu -kraa -kraak -kraaks -kraaps -kraapsti -krahh -kraks -kraksti -kraps -krapsti -krauh -krauhti -kriiks -kriiksti -kriips -kriips-kraaps -kripa-krõpa -krips-kraps -kriuh -kriuks -kriuksti -kromps -kronk -kronks -krooks -kruu -krõks -krõksti -krõpa -krõps -krõpsti -krõuh -kräu -kräuh -kräuhti -kräuks -kss -kukeleegu -kukku -kuku -kulu -kurluu -kurnäu -kuss -kussu -kõks -kõksti -kõldi -kõlks -kõlksti -kõll -kõmaki -kõmdi -kõmm -kõmps -kõpp -kõps -kõpsadi -kõpsat -kõpsti -kõrr -kõrra-kõrra -kõss -kõtt -kõõksti -kärr -kärts -kärtsti -käuks -käuksti -kääga -kääks -kääksti -köh -köki-möki -köksti -laks -laksti -lampsti -larts -lartsti -lats -latsti -leelo -legoo -lehva -liiri-lõõri -lika-lõka -likat-lõkat -limpsti -lips -lipsti -lirts -lirtsaki -lirtsti -lonksti -lops -lopsti -lorts -lortsti -luks -lups -lupsti -lurts -lurtsti -lõks -lõksti -lõmps -lõmpsti -lõnks -lõnksti -lärts -lärtsti -läts -lätsti -lörts -lörtsti -lötsti -lööps -lööpsti -marss -mats -matsti -mauh -mauhti -mh -mhh -mhmh -miau -mjaa -mkm -m-mh -mnjaa -mnjah -moens -mulks -mulksti -mull-mull -mull-mull-mull -muu -muuh -mõh -mõmm -mäh -mäts -mäu -mää -möh -möh-öh-ää -möö -müh-müh -mühüh -müks -müksti -müraki -mürr -mürts -mürtsaki -mürtsti -mütaku -müta-mäta -müta-müta -müt-müt -müt-müt-müt -müts -mütsti -mütt -naa -naah -nah -naks -naksti -nanuu -naps -napsti -nilpsti -nipsti -nirr -niuh -niuh-näuh -niuhti -noh -noksti -nolpsti -nonoh -nonoo -nonäh -noo -nooh -nooks -norr -nurr -nuuts -nõh -nõhh -nõka-nõka -nõks -nõksat-nõksat -nõks-nõks -nõksti -nõõ -nõõh -näeh -näh -nälpsti -nämm-nämm -näpsti -näts -nätsti -näu -näuh -näuhti -näuks -näuksti -nääh -nääks -nühkat-nühkat -oeh -oh -ohh -ohhh -oh-hoi -oh-hoo -ohoh -oh-oh-oo -oh-oh-hoo -ohoi -ohoo -oi -oih -oijee -oijeh -oo -ooh -oo-oh -oo-ohh -oot -ossa -ot -paa -pah -pahh -pakaa -pamm -pantsti -pardon -pardonks -parlartsti -parts -partsti -partsumdi -partsumm -pastoi -pats -patst -patsti -pau -pauh -pauhti -pele -pfui -phuh -phuuh -phäh -phähh -piiks -piip -piiri-pääri -pimm -pimm-pamm -pimm-pomm -pimm-põmm -piraki -piuks -piu-pau -plaks -plaksti -plarts -plartsti -plats -platsti -plauh -plauhh -plauhti -pliks -pliks-plaks -plinn -pliraki -plirts -plirtsti -pliu -pliuh -ploks -plotsti -plumps -plumpsti -plõks -plõksti -plõmdi -plõmm -plõnn -plärr -plärts -plärtsat -plärtsti -pläu -pläuh -plää -plörtsat -pomm -popp -pops -popsti -ports -pot -pots -potsti -pott -praks -praksti -prants -prantsaki -prantsti -prassai -prauh -prauhh -prauhti -priks -priuh -priuhh -priuh-prauh -proosit -proost -prr -prrr -prõks -prõksti -prõmdi -prõmm -prõntsti -prääk -prääks -pst -psst -ptrr -ptruu -ptüi -puh -puhh -puksti -pumm -pumps -pup-pup-pup -purts -puuh -põks -põksti -põmdi -põmm -põmmadi -põnks -põnn -põnnadi -põnt -põnts -põntsti -põraki -põrr -põrra-põrra -päh -pähh -päntsti -pää -pöörd -püh -raks -raksti -raps -rapsti -ratataa -rauh -riips -riipsti -riks -riks-raks -rips-raps -rivitult -robaki -rops -ropsaki -ropsti -ruik -räntsti -räts -röh -röhh -sah -sahh -sahkat -saps -sapsti -sauh -sauhti -servus -sihkadi-sahkadi -sihka-sahka -sihkat-sahkat -silks -silk-solk -sips -sipsti -sirr -sirr-sorr -sirts -sirtsti -siu -siuh -siuh-sauh -siuh-säuh -siuhti -siuks -siuts -skool -so -soh -solks -solksti -solpsti -soo -sooh -so-oh -soo-oh -sopp -sops -sopsti -sorr -sorts -sortsti -so-soo -soss -soss-soss -ss -sss -sst -stopp -suhkat-sahkat -sulk -sulks -sulksti -sull -sulla-sulla -sulpa-sulpa -sulps -sulpsti -sumaki -sumdi -summ -summat-summat -sups -supsaku -supsti -surts -surtsti -suss -susti -suts -sutsti -säh -sähke -särts -särtsti -säu -säuh -säuhti -taevake -taevakene -takk -tere -terekest -tibi-tibi -tikk-takk -tiks -tilk -tilks -till -tilla-talla -till-tall -tilulii -tinn -tip -tip-tap -tirr -tirtsti -tiu -tjaa -tjah -tohhoh -tohhoo -tohoh -tohoo -tok -tokk -toks -toksti -tonks -tonksti -tota -totsti -tot-tot -tprr -tpruu -trah -trahh -trallallaa -trill -trillallaa -trr -trrr -tsah -tsahh -tsilk -tsilk-tsolk -tsirr -tsiuh -tskae -tsolk -tss -tst -tsst -tsuhh -tsuk -tsumm -tsurr -tsäuh -tšao -tšš -tššš -tuk -tuks -turts -turtsti -tutki -tutkit -tutu-lutu -tutulutu -tuut -tuutu-luutu -tõks -tötsti -tümps -uh -uhh -uh-huu -uhtsa -uhtsaa -uhuh -uhuu -ui -uih -uih-aih -uijah -uijeh -uist -uit -uka -upsti -uraa -urjah -urjeh -urjoh -urjuh -urr -urraa -ust -utu -uu -uuh -vaak -vaat -vae -vaeh -vai -vat -vau -vhüüt -vidiit -viiks -vilks -vilksti -vinki-vinki -virdi -virr -viu -viudi -viuh -viuhti -voeh -voh -vohh -volks -volksti -vooh -vops -vopsti -vot -vuh -vuhti -vuih -vulks -vulksti -vull -vulpsti -vups -vupsaki -vupsaku -vupsti -vurdi -vurr -vurra-vurra -vurts -vurtsti -vutt -võe -võeh -või -võih -võrr -võts -võtt -vääks -õe -õits -õk -õkk -õrr -õss -õuh -äh -ähh -ähhähhää -äh-hää -äh-äh-hää -äiu -äiu-ää -äss -ää -ääh -äähh -öh -öhh -ök -üh -eelmine -eikeegi -eimiski -emb-kumb -enam -enim -iga -igasugune -igaüks -ise -isesugune -järgmine -keegi -kes -kumb -kumbki -kõik -meiesugune -meietaoline -midagi -mihuke -mihukene -milletaoline -milline -mina -minake -mingi -mingisugune -minusugune -minutaoline -mis -miski -miskisugune -missugune -misuke -mitmes -mitmesugune -mitu -mitu-mitu -mitu-setu -muu -mõlema -mõnesugune -mõni -mõningane -mõningas -mäherdune -määrane -naasugune -need -nemad -nendesugune -nendetaoline -nihuke -nihukene -niimitu -niisamasugune -niisugune -nisuke -nisukene -oma -omaenese -omasugune -omataoline -pool -praegune -sama -samasugune -samataoline -see -seesama -seesamane -seesamune -seesinane -seesugune -selline -sihuke -sihukene -sina -sinusugune -sinutaoline -siuke -siukene -säherdune -säärane -taoline -teiesugune -teine -teistsugune -tema -temake -temakene -temasugune -temataoline -too -toosama -toosamane -üks -üksteise -hakkama -minema -olema -pidama -saama -tegema -tulema -võima diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_eu.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_eu.txt deleted file mode 100644 index 25f1db9346..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_eu.txt +++ /dev/null @@ -1,99 +0,0 @@ -# example set of basque stopwords -al -anitz -arabera -asko -baina -bat -batean -batek -bati -batzuei -batzuek -batzuetan -batzuk -bera -beraiek -berau -berauek -bere -berori -beroriek -beste -bezala -da -dago -dira -ditu -du -dute -edo -egin -ere -eta -eurak -ez -gainera -gu -gutxi -guzti -haiei -haiek -haietan -hainbeste -hala -han -handik -hango -hara -hari -hark -hartan -hau -hauei -hauek -hauetan -hemen -hemendik -hemengo -hi -hona -honek -honela -honetan -honi -hor -hori -horiei -horiek -horietan -horko -horra -horrek -horrela -horretan -horri -hortik -hura -izan -ni -noiz -nola -non -nondik -nongo -nor -nora -ze -zein -zen -zenbait -zenbat -zer -zergatik -ziren -zituen -zu -zuek -zuen -zuten diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_fa.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_fa.txt deleted file mode 100644 index 723641c6da..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_fa.txt +++ /dev/null @@ -1,313 +0,0 @@ -# This file was created by Jacques Savoy and is distributed under the BSD license. -# See http://members.unine.ch/jacques.savoy/clef/index.html. -# Also see http://www.opensource.org/licenses/bsd-license.html -# Note: by default this file is used after normalization, so when adding entries -# to this file, use the arabic 'ي' instead of 'ی' -انان -نداشته -سراسر -خياه -ايشان -وي -تاكنون -بيشتري -دوم -پس -ناشي -وگو -يا -داشتند -سپس -هنگام -هرگز -پنج -نشان -امسال -ديگر -گروهي -شدند -چطور -ده -و -دو -نخستين -ولي -چرا -چه -وسط -ه -كدام -قابل -يك -رفت -هفت -همچنين -در -هزار -بله -بلي -شايد -اما -شناسي -گرفته -دهد -داشته -دانست -داشتن -خواهيم -ميليارد -وقتيكه -امد -خواهد -جز -اورده -شده -بلكه -خدمات -شدن -برخي -نبود -بسياري -جلوگيري -حق -كردند -نوعي -بعري -نكرده -نظير -نبايد -بوده -بودن -داد -اورد -هست -جايي -شود -دنبال -داده -بايد -سابق -هيچ -همان -انجا -كمتر -كجاست -گردد -كسي -تر -مردم -تان -دادن -بودند -سري -جدا -ندارند -مگر -يكديگر -دارد -دهند -بنابراين -هنگامي -سمت -جا -انچه -خود -دادند -زياد -دارند -اثر -بدون -بهترين -بيشتر -البته -به -براساس -بيرون -كرد -بعضي -گرفت -توي -اي -ميليون -او -جريان -تول -بر -مانند -برابر -باشيم -مدتي -گويند -اكنون -تا -تنها -جديد -چند -بي -نشده -كردن -كردم -گويد -كرده -كنيم -نمي -نزد -روي -قصد -فقط -بالاي -ديگران -اين -ديروز -توسط -سوم -ايم -دانند -سوي -استفاده -شما -كنار -داريم -ساخته -طور -امده -رفته -نخست -بيست -نزديك -طي -كنيد -از -انها -تمامي -داشت -يكي -طريق -اش -چيست -روب -نمايد -گفت -چندين -چيزي -تواند -ام -ايا -با -ان -ايد -ترين -اينكه -ديگري -راه -هايي -بروز -همچنان -پاعين -كس -حدود -مختلف -مقابل -چيز -گيرد -ندارد -ضد -همچون -سازي -شان -مورد -باره -مرسي -خويش -برخوردار -چون -خارج -شش -هنوز -تحت -ضمن -هستيم -گفته -فكر -بسيار -پيش -براي -روزهاي -انكه -نخواهد -بالا -كل -وقتي -كي -چنين -كه -گيري -نيست -است -كجا -كند -نيز -يابد -بندي -حتي -توانند -عقب -خواست -كنند -بين -تمام -همه -ما -باشند -مثل -شد -اري -باشد -اره -طبق -بعد -اگر -صورت -غير -جاي -بيش -ريزي -اند -زيرا -چگونه -بار -لطفا -مي -درباره -من -ديده -همين -گذاري -برداري -علت -گذاشته -هم -فوق -نه -ها -شوند -اباد -همواره -هر -اول -خواهند -چهار -نام -امروز -مان -هاي -قبل -كنم -سعي -تازه -را -هستند -زير -جلوي -عنوان -بود diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_fi.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_fi.txt deleted file mode 100644 index 4372c9a055..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_fi.txt +++ /dev/null @@ -1,97 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/finnish/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - -| forms of BE - -olla -olen -olet -on -olemme -olette -ovat -ole | negative form - -oli -olisi -olisit -olisin -olisimme -olisitte -olisivat -olit -olin -olimme -olitte -olivat -ollut -olleet - -en | negation -et -ei -emme -ette -eivät - -|Nom Gen Acc Part Iness Elat Illat Adess Ablat Allat Ess Trans -minä minun minut minua minussa minusta minuun minulla minulta minulle | I -sinä sinun sinut sinua sinussa sinusta sinuun sinulla sinulta sinulle | you -hän hänen hänet häntä hänessä hänestä häneen hänellä häneltä hänelle | he she -me meidän meidät meitä meissä meistä meihin meillä meiltä meille | we -te teidän teidät teitä teissä teistä teihin teillä teiltä teille | you -he heidän heidät heitä heissä heistä heihin heillä heiltä heille | they - -tämä tämän tätä tässä tästä tähän tallä tältä tälle tänä täksi | this -tuo tuon tuotä tuossa tuosta tuohon tuolla tuolta tuolle tuona tuoksi | that -se sen sitä siinä siitä siihen sillä siltä sille sinä siksi | it -nämä näiden näitä näissä näistä näihin näillä näiltä näille näinä näiksi | these -nuo noiden noita noissa noista noihin noilla noilta noille noina noiksi | those -ne niiden niitä niissä niistä niihin niillä niiltä niille niinä niiksi | they - -kuka kenen kenet ketä kenessä kenestä keneen kenellä keneltä kenelle kenenä keneksi| who -ketkä keiden ketkä keitä keissä keistä keihin keillä keiltä keille keinä keiksi | (pl) -mikä minkä minkä mitä missä mistä mihin millä miltä mille minä miksi | which what -mitkä | (pl) - -joka jonka jota jossa josta johon jolla jolta jolle jona joksi | who which -jotka joiden joita joissa joista joihin joilla joilta joille joina joiksi | (pl) - -| conjunctions - -että | that -ja | and -jos | if -koska | because -kuin | than -mutta | but -niin | so -sekä | and -sillä | for -tai | or -vaan | but -vai | or -vaikka | although - - -| prepositions - -kanssa | with -mukaan | according to -noin | about -poikki | across -yli | over, across - -| other - -kun | when -niin | so -nyt | now -itse | self - diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_fr.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_fr.txt deleted file mode 100644 index 749abae684..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_fr.txt +++ /dev/null @@ -1,186 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/french/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | A French stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - -au | a + le -aux | a + les -avec | with -ce | this -ces | these -dans | with -de | of -des | de + les -du | de + le -elle | she -en | `of them' etc -et | and -eux | them -il | he -je | I -la | the -le | the -leur | their -lui | him -ma | my (fem) -mais | but -me | me -même | same; as in moi-même (myself) etc -mes | me (pl) -moi | me -mon | my (masc) -ne | not -nos | our (pl) -notre | our -nous | we -on | one -ou | where -par | by -pas | not -pour | for -qu | que before vowel -que | that -qui | who -sa | his, her (fem) -se | oneself -ses | his (pl) -son | his, her (masc) -sur | on -ta | thy (fem) -te | thee -tes | thy (pl) -toi | thee -ton | thy (masc) -tu | thou -un | a -une | a -vos | your (pl) -votre | your -vous | you - - | single letter forms - -c | c' -d | d' -j | j' -l | l' -à | to, at -m | m' -n | n' -s | s' -t | t' -y | there - - | forms of être (not including the infinitive): -été -étée -étées -étés -étant -suis -es -est -sommes -êtes -sont -serai -seras -sera -serons -serez -seront -serais -serait -serions -seriez -seraient -étais -était -étions -étiez -étaient -fus -fut -fûmes -fûtes -furent -sois -soit -soyons -soyez -soient -fusse -fusses -fût -fussions -fussiez -fussent - - | forms of avoir (not including the infinitive): -ayant -eu -eue -eues -eus -ai -as -avons -avez -ont -aurai -auras -aura -aurons -aurez -auront -aurais -aurait -aurions -auriez -auraient -avais -avait -avions -aviez -avaient -eut -eûmes -eûtes -eurent -aie -aies -ait -ayons -ayez -aient -eusse -eusses -eût -eussions -eussiez -eussent - - | Later additions (from Jean-Christophe Deschamps) -ceci | this -cela | that -celà | that -cet | this -cette | this -ici | here -ils | they -les | the (pl) -leurs | their (pl) -quel | which -quels | which -quelle | which -quelles | which -sans | without -soi | oneself - diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ga.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ga.txt deleted file mode 100644 index 9ff88d747e..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_ga.txt +++ /dev/null @@ -1,110 +0,0 @@ - -a -ach -ag -agus -an -aon -ar -arna -as -b' -ba -beirt -bhúr -caoga -ceathair -ceathrar -chomh -chtó -chuig -chun -cois -céad -cúig -cúigear -d' -daichead -dar -de -deich -deichniúr -den -dhá -do -don -dtí -dá -dár -dó -faoi -faoin -faoina -faoinár -fara -fiche -gach -gan -go -gur -haon -hocht -i -iad -idir -in -ina -ins -inár -is -le -leis -lena -lenár -m' -mar -mo -mé -na -nach -naoi -naonúr -ná -ní -níor -nó -nócha -ocht -ochtar -os -roimh -sa -seacht -seachtar -seachtó -seasca -seisear -siad -sibh -sinn -sna -sé -sí -tar -thar -thú -triúr -trí -trína -trínár -tríocha -tú -um -ár -é -éis -í -ó -ón -óna -ónár diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_gl.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_gl.txt deleted file mode 100644 index d8760b12c1..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_gl.txt +++ /dev/null @@ -1,161 +0,0 @@ -# galican stopwords -a -aínda -alí -aquel -aquela -aquelas -aqueles -aquilo -aquí -ao -aos -as -así -á -ben -cando -che -co -coa -comigo -con -connosco -contigo -convosco -coas -cos -cun -cuns -cunha -cunhas -da -dalgunha -dalgunhas -dalgún -dalgúns -das -de -del -dela -delas -deles -desde -deste -do -dos -dun -duns -dunha -dunhas -e -el -ela -elas -eles -en -era -eran -esa -esas -ese -eses -esta -estar -estaba -está -están -este -estes -estiven -estou -eu -é -facer -foi -foron -fun -había -hai -iso -isto -la -las -lle -lles -lo -los -mais -me -meu -meus -min -miña -miñas -moi -na -nas -neste -nin -no -non -nos -nosa -nosas -noso -nosos -nós -nun -nunha -nuns -nunhas -o -os -ou -ó -ós -para -pero -pode -pois -pola -polas -polo -polos -por -que -se -senón -ser -seu -seus -sexa -sido -sobre -súa -súas -tamén -tan -te -ten -teñen -teño -ter -teu -teus -ti -tido -tiña -tiven -túa -túas -un -unha -unhas -uns -vos -vosa -vosas -voso -vosos -vós diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_hi.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_hi.txt deleted file mode 100644 index 86286bb083..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_hi.txt +++ /dev/null @@ -1,235 +0,0 @@ -# Also see http://www.opensource.org/licenses/bsd-license.html -# See http://members.unine.ch/jacques.savoy/clef/index.html. -# This file was created by Jacques Savoy and is distributed under the BSD license. -# Note: by default this file also contains forms normalized by HindiNormalizer -# for spelling variation (see section below), such that it can be used whether or -# not you enable that feature. When adding additional entries to this list, -# please add the normalized form as well. -अंदर -अत -अपना -अपनी -अपने -अभी -आदि -आप -इत्यादि -इन -इनका -इन्हीं -इन्हें -इन्हों -इस -इसका -इसकी -इसके -इसमें -इसी -इसे -उन -उनका -उनकी -उनके -उनको -उन्हीं -उन्हें -उन्हों -उस -उसके -उसी -उसे -एक -एवं -एस -ऐसे -और -कई -कर -करता -करते -करना -करने -करें -कहते -कहा -का -काफ़ी -कि -कितना -किन्हें -किन्हों -किया -किर -किस -किसी -किसे -की -कुछ -कुल -के -को -कोई -कौन -कौनसा -गया -घर -जब -जहाँ -जा -जितना -जिन -जिन्हें -जिन्हों -जिस -जिसे -जीधर -जैसा -जैसे -जो -तक -तब -तरह -तिन -तिन्हें -तिन्हों -तिस -तिसे -तो -था -थी -थे -दबारा -दिया -दुसरा -दूसरे -दो -द्वारा -न -नहीं -ना -निहायत -नीचे -ने -पर -पर -पहले -पूरा -पे -फिर -बनी -बही -बहुत -बाद -बाला -बिलकुल -भी -भीतर -मगर -मानो -मे -में -यदि -यह -यहाँ -यही -या -यिह -ये -रखें -रहा -रहे -ऱ्वासा -लिए -लिये -लेकिन -व -वर्ग -वह -वह -वहाँ -वहीं -वाले -वुह -वे -वग़ैरह -संग -सकता -सकते -सबसे -सभी -साथ -साबुत -साभ -सारा -से -सो -ही -हुआ -हुई -हुए -है -हैं -हो -होता -होती -होते -होना -होने -# additional normalized forms of the above -अपनि -जेसे -होति -सभि -तिंहों -इंहों -दवारा -इसि -किंहें -थि -उंहों -ओर -जिंहें -वहिं -अभि -बनि -हि -उंहिं -उंहें -हें -वगेरह -एसे -रवासा -कोन -निचे -काफि -उसि -पुरा -भितर -हे -बहि -वहां -कोइ -यहां -जिंहों -तिंहें -किसि -कइ -यहि -इंहिं -जिधर -इंहें -अदि -इतयादि -हुइ -कोनसा -इसकि -दुसरे -जहां -अप -किंहों -उनकि -भि -वरग -हुअ -जेसा -नहिं diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_hu.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_hu.txt deleted file mode 100644 index 37526da8aa..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_hu.txt +++ /dev/null @@ -1,211 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/hungarian/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - -| Hungarian stop word list -| prepared by Anna Tordai - -a -ahogy -ahol -aki -akik -akkor -alatt -által -általában -amely -amelyek -amelyekben -amelyeket -amelyet -amelynek -ami -amit -amolyan -amíg -amikor -át -abban -ahhoz -annak -arra -arról -az -azok -azon -azt -azzal -azért -aztán -azután -azonban -bár -be -belül -benne -cikk -cikkek -cikkeket -csak -de -e -eddig -egész -egy -egyes -egyetlen -egyéb -egyik -egyre -ekkor -el -elég -ellen -elő -először -előtt -első -én -éppen -ebben -ehhez -emilyen -ennek -erre -ez -ezt -ezek -ezen -ezzel -ezért -és -fel -felé -hanem -hiszen -hogy -hogyan -igen -így -illetve -ill. -ill -ilyen -ilyenkor -ison -ismét -itt -jó -jól -jobban -kell -kellett -keresztül -keressünk -ki -kívül -között -közül -legalább -lehet -lehetett -legyen -lenne -lenni -lesz -lett -maga -magát -majd -majd -már -más -másik -meg -még -mellett -mert -mely -melyek -mi -mit -míg -miért -milyen -mikor -minden -mindent -mindenki -mindig -mint -mintha -mivel -most -nagy -nagyobb -nagyon -ne -néha -nekem -neki -nem -néhány -nélkül -nincs -olyan -ott -össze -ő -ők -őket -pedig -persze -rá -s -saját -sem -semmi -sok -sokat -sokkal -számára -szemben -szerint -szinte -talán -tehát -teljes -tovább -továbbá -több -úgy -ugyanis -új -újabb -újra -után -utána -utolsó -vagy -vagyis -valaki -valami -valamint -való -vagyok -van -vannak -volt -voltam -voltak -voltunk -vissza -vele -viszont -volna diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_hy.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_hy.txt deleted file mode 100644 index 60c1c50fbc..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_hy.txt +++ /dev/null @@ -1,46 +0,0 @@ -# example set of Armenian stopwords. -այդ -այլ -այն -այս -դու -դուք -եմ -են -ենք -ես -եք -է -էի -էին -էինք -էիր -էիք -էր -ըստ -թ -ի -ին -իսկ -իր -կամ -համար -հետ -հետո -մենք -մեջ -մի -ն -նա -նաև -նրա -նրանք -որ -որը -որոնք -որպես -ու -ում -պիտի -վրա -և diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_id.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_id.txt deleted file mode 100644 index 4617f83a5c..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_id.txt +++ /dev/null @@ -1,359 +0,0 @@ -# from appendix D of: A Study of Stemming Effects on Information -# Retrieval in Bahasa Indonesia -ada -adanya -adalah -adapun -agak -agaknya -agar -akan -akankah -akhirnya -aku -akulah -amat -amatlah -anda -andalah -antar -diantaranya -antara -antaranya -diantara -apa -apaan -mengapa -apabila -apakah -apalagi -apatah -atau -ataukah -ataupun -bagai -bagaikan -sebagai -sebagainya -bagaimana -bagaimanapun -sebagaimana -bagaimanakah -bagi -bahkan -bahwa -bahwasanya -sebaliknya -banyak -sebanyak -beberapa -seberapa -begini -beginian -beginikah -beginilah -sebegini -begitu -begitukah -begitulah -begitupun -sebegitu -belum -belumlah -sebelum -sebelumnya -sebenarnya -berapa -berapakah -berapalah -berapapun -betulkah -sebetulnya -biasa -biasanya -bila -bilakah -bisa -bisakah -sebisanya -boleh -bolehkah -bolehlah -buat -bukan -bukankah -bukanlah -bukannya -cuma -percuma -dahulu -dalam -dan -dapat -dari -daripada -dekat -demi -demikian -demikianlah -sedemikian -dengan -depan -di -dia -dialah -dini -diri -dirinya -terdiri -dong -dulu -enggak -enggaknya -entah -entahlah -terhadap -terhadapnya -hal -hampir -hanya -hanyalah -harus -haruslah -harusnya -seharusnya -hendak -hendaklah -hendaknya -hingga -sehingga -ia -ialah -ibarat -ingin -inginkah -inginkan -ini -inikah -inilah -itu -itukah -itulah -jangan -jangankan -janganlah -jika -jikalau -juga -justru -kala -kalau -kalaulah -kalaupun -kalian -kami -kamilah -kamu -kamulah -kan -kapan -kapankah -kapanpun -dikarenakan -karena -karenanya -ke -kecil -kemudian -kenapa -kepada -kepadanya -ketika -seketika -khususnya -kini -kinilah -kiranya -sekiranya -kita -kitalah -kok -lagi -lagian -selagi -lah -lain -lainnya -melainkan -selaku -lalu -melalui -terlalu -lama -lamanya -selama -selama -selamanya -lebih -terlebih -bermacam -macam -semacam -maka -makanya -makin -malah -malahan -mampu -mampukah -mana -manakala -manalagi -masih -masihkah -semasih -masing -mau -maupun -semaunya -memang -mereka -merekalah -meski -meskipun -semula -mungkin -mungkinkah -nah -namun -nanti -nantinya -nyaris -oleh -olehnya -seorang -seseorang -pada -padanya -padahal -paling -sepanjang -pantas -sepantasnya -sepantasnyalah -para -pasti -pastilah -per -pernah -pula -pun -merupakan -rupanya -serupa -saat -saatnya -sesaat -saja -sajalah -saling -bersama -sama -sesama -sambil -sampai -sana -sangat -sangatlah -saya -sayalah -se -sebab -sebabnya -sebuah -tersebut -tersebutlah -sedang -sedangkan -sedikit -sedikitnya -segala -segalanya -segera -sesegera -sejak -sejenak -sekali -sekalian -sekalipun -sesekali -sekaligus -sekarang -sekarang -sekitar -sekitarnya -sela -selain -selalu -seluruh -seluruhnya -semakin -sementara -sempat -semua -semuanya -sendiri -sendirinya -seolah -seperti -sepertinya -sering -seringnya -serta -siapa -siapakah -siapapun -disini -disinilah -sini -sinilah -sesuatu -sesuatunya -suatu -sesudah -sesudahnya -sudah -sudahkah -sudahlah -supaya -tadi -tadinya -tak -tanpa -setelah -telah -tentang -tentu -tentulah -tentunya -tertentu -seterusnya -tapi -tetapi -setiap -tiap -setidaknya -tidak -tidakkah -tidaklah -toh -waduh -wah -wahai -sewaktu -walau -walaupun -wong -yaitu -yakni -yang diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_it.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_it.txt deleted file mode 100644 index 1219cc773a..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_it.txt +++ /dev/null @@ -1,303 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/italian/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | An Italian stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - -ad | a (to) before vowel -al | a + il -allo | a + lo -ai | a + i -agli | a + gli -all | a + l' -agl | a + gl' -alla | a + la -alle | a + le -con | with -col | con + il -coi | con + i (forms collo, cogli etc are now very rare) -da | from -dal | da + il -dallo | da + lo -dai | da + i -dagli | da + gli -dall | da + l' -dagl | da + gll' -dalla | da + la -dalle | da + le -di | of -del | di + il -dello | di + lo -dei | di + i -degli | di + gli -dell | di + l' -degl | di + gl' -della | di + la -delle | di + le -in | in -nel | in + el -nello | in + lo -nei | in + i -negli | in + gli -nell | in + l' -negl | in + gl' -nella | in + la -nelle | in + le -su | on -sul | su + il -sullo | su + lo -sui | su + i -sugli | su + gli -sull | su + l' -sugl | su + gl' -sulla | su + la -sulle | su + le -per | through, by -tra | among -contro | against -io | I -tu | thou -lui | he -lei | she -noi | we -voi | you -loro | they -mio | my -mia | -miei | -mie | -tuo | -tua | -tuoi | thy -tue | -suo | -sua | -suoi | his, her -sue | -nostro | our -nostra | -nostri | -nostre | -vostro | your -vostra | -vostri | -vostre | -mi | me -ti | thee -ci | us, there -vi | you, there -lo | him, the -la | her, the -li | them -le | them, the -gli | to him, the -ne | from there etc -il | the -un | a -uno | a -una | a -ma | but -ed | and -se | if -perché | why, because -anche | also -come | how -dov | where (as dov') -dove | where -che | who, that -chi | who -cui | whom -non | not -più | more -quale | who, that -quanto | how much -quanti | -quanta | -quante | -quello | that -quelli | -quella | -quelle | -questo | this -questi | -questa | -queste | -si | yes -tutto | all -tutti | all - - | single letter forms: - -a | at -c | as c' for ce or ci -e | and -i | the -l | as l' -o | or - - | forms of avere, to have (not including the infinitive): - -ho -hai -ha -abbiamo -avete -hanno -abbia -abbiate -abbiano -avrò -avrai -avrà -avremo -avrete -avranno -avrei -avresti -avrebbe -avremmo -avreste -avrebbero -avevo -avevi -aveva -avevamo -avevate -avevano -ebbi -avesti -ebbe -avemmo -aveste -ebbero -avessi -avesse -avessimo -avessero -avendo -avuto -avuta -avuti -avute - - | forms of essere, to be (not including the infinitive): -sono -sei -è -siamo -siete -sia -siate -siano -sarò -sarai -sarà -saremo -sarete -saranno -sarei -saresti -sarebbe -saremmo -sareste -sarebbero -ero -eri -era -eravamo -eravate -erano -fui -fosti -fu -fummo -foste -furono -fossi -fosse -fossimo -fossero -essendo - - | forms of fare, to do (not including the infinitive, fa, fat-): -faccio -fai -facciamo -fanno -faccia -facciate -facciano -farò -farai -farà -faremo -farete -faranno -farei -faresti -farebbe -faremmo -fareste -farebbero -facevo -facevi -faceva -facevamo -facevate -facevano -feci -facesti -fece -facemmo -faceste -fecero -facessi -facesse -facessimo -facessero -facendo - - | forms of stare, to be (not including the infinitive): -sto -stai -sta -stiamo -stanno -stia -stiate -stiano -starò -starai -starà -staremo -starete -staranno -starei -staresti -starebbe -staremmo -stareste -starebbero -stavo -stavi -stava -stavamo -stavate -stavano -stetti -stesti -stette -stemmo -steste -stettero -stessi -stesse -stessimo -stessero -stando diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ja.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ja.txt deleted file mode 100644 index d4321be6b1..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_ja.txt +++ /dev/null @@ -1,127 +0,0 @@ -# -# This file defines a stopword set for Japanese. -# -# This set is made up of hand-picked frequent terms from segmented Japanese Wikipedia. -# Punctuation characters and frequent kanji have mostly been left out. See LUCENE-3745 -# for frequency lists, etc. that can be useful for making your own set (if desired) -# -# Note that there is an overlap between these stopwords and the terms stopped when used -# in combination with the JapanesePartOfSpeechStopFilter. When editing this file, note -# that comments are not allowed on the same line as stopwords. -# -# Also note that stopping is done in a case-insensitive manner. Change your StopFilter -# configuration if you need case-sensitive stopping. Lastly, note that stopping is done -# using the same character width as the entries in this file. Since this StopFilter is -# normally done after a CJKWidthFilter in your chain, you would usually want your romaji -# entries to be in half-width and your kana entries to be in full-width. -# -の -に -は -を -た -が -で -て -と -し -れ -さ -ある -いる -も -する -から -な -こと -として -い -や -れる -など -なっ -ない -この -ため -その -あっ -よう -また -もの -という -あり -まで -られ -なる -へ -か -だ -これ -によって -により -おり -より -による -ず -なり -られる -において -ば -なかっ -なく -しかし -について -せ -だっ -その後 -できる -それ -う -ので -なお -のみ -でき -き -つ -における -および -いう -さらに -でも -ら -たり -その他 -に関する -たち -ます -ん -なら -に対して -特に -せる -及び -これら -とき -では -にて -ほか -ながら -うち -そして -とともに -ただし -かつて -それぞれ -または -お -ほど -ものの -に対する -ほとんど -と共に -といった -です -とも -ところ -ここ -##### End of file diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_lv.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_lv.txt deleted file mode 100644 index e21a23c06c..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_lv.txt +++ /dev/null @@ -1,172 +0,0 @@ -# Set of Latvian stopwords from A Stemming Algorithm for Latvian, Karlis Kreslins -# the original list of over 800 forms was refined: -# pronouns, adverbs, interjections were removed -# -# prepositions -aiz -ap -ar -apakš -ārpus -augšpus -bez -caur -dēļ -gar -iekš -iz -kopš -labad -lejpus -līdz -no -otrpus -pa -par -pār -pēc -pie -pirms -pret -priekš -starp -šaipus -uz -viņpus -virs -virspus -zem -apakšpus -# Conjunctions -un -bet -jo -ja -ka -lai -tomēr -tikko -turpretī -arī -kaut -gan -tādēļ -tā -ne -tikvien -vien -kā -ir -te -vai -kamēr -# Particles -ar -diezin -droši -diemžēl -nebūt -ik -it -taču -nu -pat -tiklab -iekšpus -nedz -tik -nevis -turpretim -jeb -iekam -iekām -iekāms -kolīdz -līdzko -tiklīdz -jebšu -tālab -tāpēc -nekā -itin -jā -jau -jel -nē -nezin -tad -tikai -vis -tak -iekams -vien -# modal verbs -būt -biju -biji -bija -bijām -bijāt -esmu -esi -esam -esat -būšu -būsi -būs -būsim -būsiet -tikt -tiku -tiki -tika -tikām -tikāt -tieku -tiec -tiek -tiekam -tiekat -tikšu -tiks -tiksim -tiksiet -tapt -tapi -tapāt -topat -tapšu -tapsi -taps -tapsim -tapsiet -kļūt -kļuvu -kļuvi -kļuva -kļuvām -kļuvāt -kļūstu -kļūsti -kļūst -kļūstam -kļūstat -kļūšu -kļūsi -kļūs -kļūsim -kļūsiet -# verbs -varēt -varēju -varējām -varēšu -varēsim -var -varēji -varējāt -varēsi -varēsiet -varat -varēja -varēs diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_nl.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_nl.txt deleted file mode 100644 index 47a2aeacf6..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_nl.txt +++ /dev/null @@ -1,119 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/dutch/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | A Dutch stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - - | This is a ranked list (commonest to rarest) of stopwords derived from - | a large sample of Dutch text. - - | Dutch stop words frequently exhibit homonym clashes. These are indicated - | clearly below. - -de | the -en | and -van | of, from -ik | I, the ego -te | (1) chez, at etc, (2) to, (3) too -dat | that, which -die | that, those, who, which -in | in, inside -een | a, an, one -hij | he -het | the, it -niet | not, nothing, naught -zijn | (1) to be, being, (2) his, one's, its -is | is -was | (1) was, past tense of all persons sing. of 'zijn' (to be) (2) wax, (3) the washing, (4) rise of river -op | on, upon, at, in, up, used up -aan | on, upon, to (as dative) -met | with, by -als | like, such as, when -voor | (1) before, in front of, (2) furrow -had | had, past tense all persons sing. of 'hebben' (have) -er | there -maar | but, only -om | round, about, for etc -hem | him -dan | then -zou | should/would, past tense all persons sing. of 'zullen' -of | or, whether, if -wat | what, something, anything -mijn | possessive and noun 'mine' -men | people, 'one' -dit | this -zo | so, thus, in this way -door | through by -over | over, across -ze | she, her, they, them -zich | oneself -bij | (1) a bee, (2) by, near, at -ook | also, too -tot | till, until -je | you -mij | me -uit | out of, from -der | Old Dutch form of 'van der' still found in surnames -daar | (1) there, (2) because -haar | (1) her, their, them, (2) hair -naar | (1) unpleasant, unwell etc, (2) towards, (3) as -heb | present first person sing. of 'to have' -hoe | how, why -heeft | present third person sing. of 'to have' -hebben | 'to have' and various parts thereof -deze | this -u | you -want | (1) for, (2) mitten, (3) rigging -nog | yet, still -zal | 'shall', first and third person sing. of verb 'zullen' (will) -me | me -zij | she, they -nu | now -ge | 'thou', still used in Belgium and south Netherlands -geen | none -omdat | because -iets | something, somewhat -worden | to become, grow, get -toch | yet, still -al | all, every, each -waren | (1) 'were' (2) to wander, (3) wares, (3) -veel | much, many -meer | (1) more, (2) lake -doen | to do, to make -toen | then, when -moet | noun 'spot/mote' and present form of 'to must' -ben | (1) am, (2) 'are' in interrogative second person singular of 'to be' -zonder | without -kan | noun 'can' and present form of 'to be able' -hun | their, them -dus | so, consequently -alles | all, everything, anything -onder | under, beneath -ja | yes, of course -eens | once, one day -hier | here -wie | who -werd | imperfect third person sing. of 'become' -altijd | always -doch | yet, but etc -wordt | present third person sing. of 'become' -wezen | (1) to be, (2) 'been' as in 'been fishing', (3) orphans -kunnen | to be able -ons | us/our -zelf | self -tegen | against, towards, at -na | after, near -reeds | already -wil | (1) present tense of 'want', (2) 'will', noun, (3) fender -kon | could; past tense of 'to be able' -niets | nothing -uw | your -iemand | somebody -geweest | been; past participle of 'be' -andere | other diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_no.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_no.txt deleted file mode 100644 index a7a2c28ba5..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_no.txt +++ /dev/null @@ -1,194 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/norwegian/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | A Norwegian stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - - | This stop word list is for the dominant bokmål dialect. Words unique - | to nynorsk are marked *. - - | Revised by Jan Bruusgaard , Jan 2005 - -og | and -i | in -jeg | I -det | it/this/that -at | to (w. inf.) -en | a/an -et | a/an -den | it/this/that -til | to -er | is/am/are -som | who/that -på | on -de | they / you(formal) -med | with -han | he -av | of -ikke | not -ikkje | not * -der | there -så | so -var | was/were -meg | me -seg | you -men | but -ett | one -har | have -om | about -vi | we -min | my -mitt | my -ha | have -hadde | had -hun | she -nå | now -over | over -da | when/as -ved | by/know -fra | from -du | you -ut | out -sin | your -dem | them -oss | us -opp | up -man | you/one -kan | can -hans | his -hvor | where -eller | or -hva | what -skal | shall/must -selv | self (reflective) -sjøl | self (reflective) -her | here -alle | all -vil | will -bli | become -ble | became -blei | became * -blitt | have become -kunne | could -inn | in -når | when -være | be -kom | come -noen | some -noe | some -ville | would -dere | you -som | who/which/that -deres | their/theirs -kun | only/just -ja | yes -etter | after -ned | down -skulle | should -denne | this -for | for/because -deg | you -si | hers/his -sine | hers/his -sitt | hers/his -mot | against -å | to -meget | much -hvorfor | why -dette | this -disse | these/those -uten | without -hvordan | how -ingen | none -din | your -ditt | your -blir | become -samme | same -hvilken | which -hvilke | which (plural) -sånn | such a -inni | inside/within -mellom | between -vår | our -hver | each -hvem | who -vors | us/ours -hvis | whose -både | both -bare | only/just -enn | than -fordi | as/because -før | before -mange | many -også | also -slik | just -vært | been -være | to be -båe | both * -begge | both -siden | since -dykk | your * -dykkar | yours * -dei | they * -deira | them * -deires | theirs * -deim | them * -di | your (fem.) * -då | as/when * -eg | I * -ein | a/an * -eit | a/an * -eitt | a/an * -elles | or * -honom | he * -hjå | at * -ho | she * -hoe | she * -henne | her -hennar | her/hers -hennes | hers -hoss | how * -hossen | how * -ikkje | not * -ingi | noone * -inkje | noone * -korleis | how * -korso | how * -kva | what/which * -kvar | where * -kvarhelst | where * -kven | who/whom * -kvi | why * -kvifor | why * -me | we * -medan | while * -mi | my * -mine | my * -mykje | much * -no | now * -nokon | some (masc./neut.) * -noka | some (fem.) * -nokor | some * -noko | some * -nokre | some * -si | his/hers * -sia | since * -sidan | since * -so | so * -somt | some * -somme | some * -um | about* -upp | up * -vere | be * -vore | was * -verte | become * -vort | become * -varte | became * -vart | became * - diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_pt.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_pt.txt deleted file mode 100644 index acfeb01af6..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_pt.txt +++ /dev/null @@ -1,253 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/portuguese/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | A Portuguese stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - - - | The following is a ranked list (commonest to rarest) of stopwords - | deriving from a large sample of text. - - | Extra words have been added at the end. - -de | of, from -a | the; to, at; her -o | the; him -que | who, that -e | and -do | de + o -da | de + a -em | in -um | a -para | for - | é from SER -com | with -não | not, no -uma | a -os | the; them -no | em + o -se | himself etc -na | em + a -por | for -mais | more -as | the; them -dos | de + os -como | as, like -mas | but - | foi from SER -ao | a + o -ele | he -das | de + as - | tem from TER -à | a + a -seu | his -sua | her -ou | or - | ser from SER -quando | when -muito | much - | há from HAV -nos | em + os; us -já | already, now - | está from EST -eu | I -também | also -só | only, just -pelo | per + o -pela | per + a -até | up to -isso | that -ela | he -entre | between - | era from SER -depois | after -sem | without -mesmo | same -aos | a + os - | ter from TER -seus | his -quem | whom -nas | em + as -me | me -esse | that -eles | they - | estão from EST -você | you - | tinha from TER - | foram from SER -essa | that -num | em + um -nem | nor -suas | her -meu | my -às | a + as -minha | my - | têm from TER -numa | em + uma -pelos | per + os -elas | they - | havia from HAV - | seja from SER -qual | which - | será from SER -nós | we - | tenho from TER -lhe | to him, her -deles | of them -essas | those -esses | those -pelas | per + as -este | this - | fosse from SER -dele | of him - - | other words. There are many contractions such as naquele = em+aquele, - | mo = me+o, but they are rare. - | Indefinite article plural forms are also rare. - -tu | thou -te | thee -vocês | you (plural) -vos | you -lhes | to them -meus | my -minhas -teu | thy -tua -teus -tuas -nosso | our -nossa -nossos -nossas - -dela | of her -delas | of them - -esta | this -estes | these -estas | these -aquele | that -aquela | that -aqueles | those -aquelas | those -isto | this -aquilo | that - - | forms of estar, to be (not including the infinitive): -estou -está -estamos -estão -estive -esteve -estivemos -estiveram -estava -estávamos -estavam -estivera -estivéramos -esteja -estejamos -estejam -estivesse -estivéssemos -estivessem -estiver -estivermos -estiverem - - | forms of haver, to have (not including the infinitive): -hei -há -havemos -hão -houve -houvemos -houveram -houvera -houvéramos -haja -hajamos -hajam -houvesse -houvéssemos -houvessem -houver -houvermos -houverem -houverei -houverá -houveremos -houverão -houveria -houveríamos -houveriam - - | forms of ser, to be (not including the infinitive): -sou -somos -são -era -éramos -eram -fui -foi -fomos -foram -fora -fôramos -seja -sejamos -sejam -fosse -fôssemos -fossem -for -formos -forem -serei -será -seremos -serão -seria -seríamos -seriam - - | forms of ter, to have (not including the infinitive): -tenho -tem -temos -tém -tinha -tínhamos -tinham -tive -teve -tivemos -tiveram -tivera -tivéramos -tenha -tenhamos -tenham -tivesse -tivéssemos -tivessem -tiver -tivermos -tiverem -terei -terá -teremos -terão -teria -teríamos -teriam diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ro.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ro.txt deleted file mode 100644 index 4fdee90a5b..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_ro.txt +++ /dev/null @@ -1,233 +0,0 @@ -# This file was created by Jacques Savoy and is distributed under the BSD license. -# See http://members.unine.ch/jacques.savoy/clef/index.html. -# Also see http://www.opensource.org/licenses/bsd-license.html -acea -aceasta -această -aceea -acei -aceia -acel -acela -acele -acelea -acest -acesta -aceste -acestea -aceşti -aceştia -acolo -acum -ai -aia -aibă -aici -al -ăla -ale -alea -ălea -altceva -altcineva -am -ar -are -aş -aşadar -asemenea -asta -ăsta -astăzi -astea -ăstea -ăştia -asupra -aţi -au -avea -avem -aveţi -azi -bine -bucur -bună -ca -că -căci -când -care -cărei -căror -cărui -cât -câte -câţi -către -câtva -ce -cel -ceva -chiar -cînd -cine -cineva -cît -cîte -cîţi -cîtva -contra -cu -cum -cumva -curând -curînd -da -dă -dacă -dar -datorită -de -deci -deja -deoarece -departe -deşi -din -dinaintea -dintr -dintre -drept -după -ea -ei -el -ele -eram -este -eşti -eu -face -fără -fi -fie -fiecare -fii -fim -fiţi -iar -ieri -îi -îl -îmi -împotriva -în -înainte -înaintea -încât -încît -încotro -între -întrucât -întrucît -îţi -la -lângă -le -li -lîngă -lor -lui -mă -mâine -mea -mei -mele -mereu -meu -mi -mine -mult -multă -mulţi -ne -nicăieri -nici -nimeni -nişte -noastră -noastre -noi -noştri -nostru -nu -ori -oricând -oricare -oricât -orice -oricînd -oricine -oricît -oricum -oriunde -până -pe -pentru -peste -pînă -poate -pot -prea -prima -primul -prin -printr -sa -să -săi -sale -sau -său -se -şi -sînt -sîntem -sînteţi -spre -sub -sunt -suntem -sunteţi -ta -tăi -tale -tău -te -ţi -ţie -tine -toată -toate -tot -toţi -totuşi -tu -un -una -unde -undeva -unei -unele -uneori -unor -vă -vi -voastră -voastre -voi -voştri -vostru -vouă -vreo -vreun diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_ru.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_ru.txt deleted file mode 100644 index 55271400c6..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_ru.txt +++ /dev/null @@ -1,243 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/russian/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | a russian stop word list. comments begin with vertical bar. each stop - | word is at the start of a line. - - | this is a ranked list (commonest to rarest) of stopwords derived from - | a large text sample. - - | letter `ё' is translated to `е'. - -и | and -в | in/into -во | alternative form -не | not -что | what/that -он | he -на | on/onto -я | i -с | from -со | alternative form -как | how -а | milder form of `no' (but) -то | conjunction and form of `that' -все | all -она | she -так | so, thus -его | him -но | but -да | yes/and -ты | thou -к | towards, by -у | around, chez -же | intensifier particle -вы | you -за | beyond, behind -бы | conditional/subj. particle -по | up to, along -только | only -ее | her -мне | to me -было | it was -вот | here is/are, particle -от | away from -меня | me -еще | still, yet, more -нет | no, there isnt/arent -о | about -из | out of -ему | to him -теперь | now -когда | when -даже | even -ну | so, well -вдруг | suddenly -ли | interrogative particle -если | if -уже | already, but homonym of `narrower' -или | or -ни | neither -быть | to be -был | he was -него | prepositional form of его -до | up to -вас | you accusative -нибудь | indef. suffix preceded by hyphen -опять | again -уж | already, but homonym of `adder' -вам | to you -сказал | he said -ведь | particle `after all' -там | there -потом | then -себя | oneself -ничего | nothing -ей | to her -может | usually with `быть' as `maybe' -они | they -тут | here -где | where -есть | there is/are -надо | got to, must -ней | prepositional form of ей -для | for -мы | we -тебя | thee -их | them, their -чем | than -была | she was -сам | self -чтоб | in order to -без | without -будто | as if -человек | man, person, one -чего | genitive form of `what' -раз | once -тоже | also -себе | to oneself -под | beneath -жизнь | life -будет | will be -ж | short form of intensifer particle `же' -тогда | then -кто | who -этот | this -говорил | was saying -того | genitive form of `that' -потому | for that reason -этого | genitive form of `this' -какой | which -совсем | altogether -ним | prepositional form of `его', `они' -здесь | here -этом | prepositional form of `этот' -один | one -почти | almost -мой | my -тем | instrumental/dative plural of `тот', `то' -чтобы | full form of `in order that' -нее | her (acc.) -кажется | it seems -сейчас | now -были | they were -куда | where to -зачем | why -сказать | to say -всех | all (acc., gen. preposn. plural) -никогда | never -сегодня | today -можно | possible, one can -при | by -наконец | finally -два | two -об | alternative form of `о', about -другой | another -хоть | even -после | after -над | above -больше | more -тот | that one (masc.) -через | across, in -эти | these -нас | us -про | about -всего | in all, only, of all -них | prepositional form of `они' (they) -какая | which, feminine -много | lots -разве | interrogative particle -сказала | she said -три | three -эту | this, acc. fem. sing. -моя | my, feminine -впрочем | moreover, besides -хорошо | good -свою | ones own, acc. fem. sing. -этой | oblique form of `эта', fem. `this' -перед | in front of -иногда | sometimes -лучше | better -чуть | a little -том | preposn. form of `that one' -нельзя | one must not -такой | such a one -им | to them -более | more -всегда | always -конечно | of course -всю | acc. fem. sing of `all' -между | between - - - | b: some paradigms - | - | personal pronouns - | - | я меня мне мной [мною] - | ты тебя тебе тобой [тобою] - | он его ему им [него, нему, ним] - | она ее эи ею [нее, нэи, нею] - | оно его ему им [него, нему, ним] - | - | мы нас нам нами - | вы вас вам вами - | они их им ими [них, ним, ними] - | - | себя себе собой [собою] - | - | demonstrative pronouns: этот (this), тот (that) - | - | этот эта это эти - | этого эты это эти - | этого этой этого этих - | этому этой этому этим - | этим этой этим [этою] этими - | этом этой этом этих - | - | тот та то те - | того ту то те - | того той того тех - | тому той тому тем - | тем той тем [тою] теми - | том той том тех - | - | determinative pronouns - | - | (a) весь (all) - | - | весь вся все все - | всего всю все все - | всего всей всего всех - | всему всей всему всем - | всем всей всем [всею] всеми - | всем всей всем всех - | - | (b) сам (himself etc) - | - | сам сама само сами - | самого саму само самих - | самого самой самого самих - | самому самой самому самим - | самим самой самим [самою] самими - | самом самой самом самих - | - | stems of verbs `to be', `to have', `to do' and modal - | - | быть бы буд быв есть суть - | име - | дел - | мог мож мочь - | уме - | хоч хот - | долж - | можн - | нужн - | нельзя - diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_sv.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_sv.txt deleted file mode 100644 index 096f87f676..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_sv.txt +++ /dev/null @@ -1,133 +0,0 @@ - | From svn.tartarus.org/snowball/trunk/website/algorithms/swedish/stop.txt - | This file is distributed under the BSD License. - | See http://snowball.tartarus.org/license.php - | Also see http://www.opensource.org/licenses/bsd-license.html - | - Encoding was converted to UTF-8. - | - This notice was added. - | - | NOTE: To use this file with StopFilterFactory, you must specify format="snowball" - - | A Swedish stop word list. Comments begin with vertical bar. Each stop - | word is at the start of a line. - - | This is a ranked list (commonest to rarest) of stopwords derived from - | a large text sample. - - | Swedish stop words occasionally exhibit homonym clashes. For example - | så = so, but also seed. These are indicated clearly below. - -och | and -det | it, this/that -att | to (with infinitive) -i | in, at -en | a -jag | I -hon | she -som | who, that -han | he -på | on -den | it, this/that -med | with -var | where, each -sig | him(self) etc -för | for -så | so (also: seed) -till | to -är | is -men | but -ett | a -om | if; around, about -hade | had -de | they, these/those -av | of -icke | not, no -mig | me -du | you -henne | her -då | then, when -sin | his -nu | now -har | have -inte | inte någon = no one -hans | his -honom | him -skulle | 'sake' -hennes | her -där | there -min | my -man | one (pronoun) -ej | nor -vid | at, by, on (also: vast) -kunde | could -något | some etc -från | from, off -ut | out -när | when -efter | after, behind -upp | up -vi | we -dem | them -vara | be -vad | what -över | over -än | than -dig | you -kan | can -sina | his -här | here -ha | have -mot | towards -alla | all -under | under (also: wonder) -någon | some etc -eller | or (else) -allt | all -mycket | much -sedan | since -ju | why -denna | this/that -själv | myself, yourself etc -detta | this/that -åt | to -utan | without -varit | was -hur | how -ingen | no -mitt | my -ni | you -bli | to be, become -blev | from bli -oss | us -din | thy -dessa | these/those -några | some etc -deras | their -blir | from bli -mina | my -samma | (the) same -vilken | who, that -er | you, your -sådan | such a -vår | our -blivit | from bli -dess | its -inom | within -mellan | between -sådant | such a -varför | why -varje | each -vilka | who, that -ditt | thy -vem | who -vilket | who, that -sitta | his -sådana | such a -vart | each -dina | thy -vars | whose -vårt | our -våra | our -ert | your -era | your -vilkas | whose - diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_th.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_th.txt deleted file mode 100644 index 07f0fabe69..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_th.txt +++ /dev/null @@ -1,119 +0,0 @@ -# Thai stopwords from: -# "Opinion Detection in Thai Political News Columns -# Based on Subjectivity Analysis" -# Khampol Sukhum, Supot Nitsuwat, and Choochart Haruechaiyasak -ไว้ -ไม่ -ไป -ได้ -ให้ -ใน -โดย -แห่ง -แล้ว -และ -แรก -แบบ -แต่ -เอง -เห็น -เลย -เริ่ม -เรา -เมื่อ -เพื่อ -เพราะ -เป็นการ -เป็น -เปิดเผย -เปิด -เนื่องจาก -เดียวกัน -เดียว -เช่น -เฉพาะ -เคย -เข้า -เขา -อีก -อาจ -อะไร -ออก -อย่าง -อยู่ -อยาก -หาก -หลาย -หลังจาก -หลัง -หรือ -หนึ่ง -ส่วน -ส่ง -สุด -สําหรับ -ว่า -วัน -ลง -ร่วม -ราย -รับ -ระหว่าง -รวม -ยัง -มี -มาก -มา -พร้อม -พบ -ผ่าน -ผล -บาง -น่า -นี้ -นํา -นั้น -นัก -นอกจาก -ทุก -ที่สุด -ที่ -ทําให้ -ทํา -ทาง -ทั้งนี้ -ทั้ง -ถ้า -ถูก -ถึง -ต้อง -ต่างๆ -ต่าง -ต่อ -ตาม -ตั้งแต่ -ตั้ง -ด้าน -ด้วย -ดัง -ซึ่ง -ช่วง -จึง -จาก -จัด -จะ -คือ -ความ -ครั้ง -คง -ขึ้น -ของ -ขอ -ขณะ -ก่อน -ก็ -การ -กับ -กัน -กว่า -กล่าว diff --git a/dspace/solr/word_highlighting/conf/lang/stopwords_tr.txt b/dspace/solr/word_highlighting/conf/lang/stopwords_tr.txt deleted file mode 100644 index 84d9408d4e..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/stopwords_tr.txt +++ /dev/null @@ -1,212 +0,0 @@ -# Turkish stopwords from LUCENE-559 -# merged with the list from "Information Retrieval on Turkish Texts" -# (http://www.users.muohio.edu/canf/papers/JASIST2008offPrint.pdf) -acaba -altmış -altı -ama -ancak -arada -aslında -ayrıca -bana -bazı -belki -ben -benden -beni -benim -beri -beş -bile -bin -bir -birçok -biri -birkaç -birkez -birşey -birşeyi -biz -bize -bizden -bizi -bizim -böyle -böylece -bu -buna -bunda -bundan -bunlar -bunları -bunların -bunu -bunun -burada -çok -çünkü -da -daha -dahi -de -defa -değil -diğer -diye -doksan -dokuz -dolayı -dolayısıyla -dört -edecek -eden -ederek -edilecek -ediliyor -edilmesi -ediyor -eğer -elli -en -etmesi -etti -ettiği -ettiğini -gibi -göre -halen -hangi -hatta -hem -henüz -hep -hepsi -her -herhangi -herkesin -hiç -hiçbir -için -iki -ile -ilgili -ise -işte -itibaren -itibariyle -kadar -karşın -katrilyon -kendi -kendilerine -kendini -kendisi -kendisine -kendisini -kez -ki -kim -kimden -kime -kimi -kimse -kırk -milyar -milyon -mu -mü -mı -nasıl -ne -neden -nedenle -nerde -nerede -nereye -niye -niçin -o -olan -olarak -oldu -olduğu -olduğunu -olduklarını -olmadı -olmadığı -olmak -olması -olmayan -olmaz -olsa -olsun -olup -olur -olursa -oluyor -on -ona -ondan -onlar -onlardan -onları -onların -onu -onun -otuz -oysa -öyle -pek -rağmen -sadece -sanki -sekiz -seksen -sen -senden -seni -senin -siz -sizden -sizi -sizin -şey -şeyden -şeyi -şeyler -şöyle -şu -şuna -şunda -şundan -şunları -şunu -tarafından -trilyon -tüm -üç -üzere -var -vardı -ve -veya -ya -yani -yapacak -yapılan -yapılması -yapıyor -yapmak -yaptı -yaptığı -yaptığını -yaptıkları -yedi -yerine -yetmiş -yine -yirmi -yoksa -yüz -zaten diff --git a/dspace/solr/word_highlighting/conf/lang/userdict_ja.txt b/dspace/solr/word_highlighting/conf/lang/userdict_ja.txt deleted file mode 100644 index 6f0368e4d8..0000000000 --- a/dspace/solr/word_highlighting/conf/lang/userdict_ja.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This is a sample user dictionary for Kuromoji (JapaneseTokenizer) -# -# Add entries to this file in order to override the statistical model in terms -# of segmentation, readings and part-of-speech tags. Notice that entries do -# not have weights since they are always used when found. This is by-design -# in order to maximize ease-of-use. -# -# Entries are defined using the following CSV format: -# , ... , ... , -# -# Notice that a single half-width space separates tokens and readings, and -# that the number tokens and readings must match exactly. -# -# Also notice that multiple entries with the same is undefined. -# -# Whitespace only lines are ignored. Comments are not allowed on entry lines. -# - -# Custom segmentation for kanji compounds -日本経済新聞,日本 経済 新聞,ニホン ケイザイ シンブン,カスタム名詞 -関西国際空港,関西 国際 空港,カンサイ コクサイ クウコウ,カスタム名詞 - -# Custom segmentation for compound katakana -トートバッグ,トート バッグ,トート バッグ,かずカナ名詞 -ショルダーバッグ,ショルダー バッグ,ショルダー バッグ,かずカナ名詞 - -# Custom reading for former sumo wrestler -朝青龍,朝青龍,アサショウリュウ,カスタム人名 diff --git a/dspace/solr/word_highlighting/conf/managed-schema b/dspace/solr/word_highlighting/conf/managed-schema deleted file mode 100644 index 5a37069bd6..0000000000 --- a/dspace/solr/word_highlighting/conf/managed-schema +++ /dev/null @@ -1,543 +0,0 @@ - - - - id - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dspace/solr/word_highlighting/conf/protwords.txt b/dspace/solr/word_highlighting/conf/protwords.txt deleted file mode 100644 index 1dfc0abecb..0000000000 --- a/dspace/solr/word_highlighting/conf/protwords.txt +++ /dev/null @@ -1,21 +0,0 @@ -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#----------------------------------------------------------------------- -# Use a protected word file to protect against the stemmer reducing two -# unrelated words to the same base word. - -# Some non-words that normally won't be encountered, -# just to test that they won't be stemmed. -dontstems -zwhacky - diff --git a/dspace/solr/word_highlighting/conf/solrconfig.xml b/dspace/solr/word_highlighting/conf/solrconfig.xml deleted file mode 100644 index ccc149c51a..0000000000 --- a/dspace/solr/word_highlighting/conf/solrconfig.xml +++ /dev/null @@ -1,1313 +0,0 @@ - - - - - - - - - 8.8.1 - - - - - - - - - - - - ${solr.data.dir:} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${solr.lock.type:native} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${solr.ulog.dir:} - ${solr.ulog.numVersionBuckets:65536} - - - - - ${solr.autoCommit.maxTime:15000} - false - - - - - - ${solr.autoSoftCommit.maxTime:-1} - - - - - - - - - - - - - - ${solr.max.booleanClauses:1024} - - - - - - - - - - - - - - - - - - - - - - - - true - - - - - - 20 - - - 200 - - - - - - - - - - - - - - - - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - explicit - 10 - - - - - - - - - - - - - - - - explicit - json - true - - - - - - _text_ - - - - - - - - - text_general - - - - - - default - _text_ - solr.DirectSolrSpellChecker - - internal - - 0.5 - - 2 - - 1 - - 5 - - 4 - - 0.01 - - - - - - - - - - - - default - on - true - 10 - 5 - 5 - true - true - 10 - 5 - - - spellcheck - - - - - - - - - - true - false - - - terms - - - - - - - query - ocrHighlight - highlight - - - - - - - - - - - 100 - - - - - - - - 70 - - 0.5 - - [-\w ,/\n\"']{20,200} - - - - - - - ]]> - ]]> - - - - - - - - - - - - - - - - - - - - - - - - ,, - ,, - ,, - ,, - ,]]> - ]]> - - - - - - 10 - .,!? - - - - - - - WORD - - - en - US - - - - - - - - - - - - [^\w-\.] - _ - - - - - - - yyyy-MM-dd['T'[HH:mm[:ss[.SSS]][z - yyyy-MM-dd['T'[HH:mm[:ss[,SSS]][z - yyyy-MM-dd HH:mm[:ss[.SSS]][z - yyyy-MM-dd HH:mm[:ss[,SSS]][z - [EEE, ]dd MMM yyyy HH:mm[:ss] z - EEEE, dd-MMM-yy HH:mm:ss z - EEE MMM ppd HH:mm:ss [z ]yyyy - - - - - java.lang.String - text_general - - *_str - 256 - - - true - - - java.lang.Boolean - booleans - - - java.util.Date - pdates - - - java.lang.Long - java.lang.Integer - plongs - - - java.lang.Number - pdoubles - - - - - - - - - - - - - - - - - - - - text/plain; charset=UTF-8 - - - - - - - - - - - - - - diff --git a/dspace/solr/word_highlighting/conf/stopwords.txt b/dspace/solr/word_highlighting/conf/stopwords.txt deleted file mode 100644 index ae1e83eeb3..0000000000 --- a/dspace/solr/word_highlighting/conf/stopwords.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/dspace/solr/word_highlighting/conf/synonyms.txt b/dspace/solr/word_highlighting/conf/synonyms.txt deleted file mode 100644 index eab4ee8753..0000000000 --- a/dspace/solr/word_highlighting/conf/synonyms.txt +++ /dev/null @@ -1,29 +0,0 @@ -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#----------------------------------------------------------------------- -#some test synonym mappings unlikely to appear in real input text -aaafoo => aaabar -bbbfoo => bbbfoo bbbbar -cccfoo => cccbar cccbaz -fooaaa,baraaa,bazaaa - -# Some synonym groups specific to this example -GB,gib,gigabyte,gigabytes -MB,mib,megabyte,megabytes -Television, Televisions, TV, TVs -#notice we use "gib" instead of "GiB" so any WordDelimiterGraphFilter coming -#after us won't split it into two words. - -# Synonym mappings can be used for spelling correction too -pixima => pixma - diff --git a/dspace/solr/word_highlighting/core.properties b/dspace/solr/word_highlighting/core.properties deleted file mode 100644 index 5ecee74676..0000000000 --- a/dspace/solr/word_highlighting/core.properties +++ /dev/null @@ -1,3 +0,0 @@ -#Written by CorePropertiesLocator -#Sun Mar 28 20:37:08 UTC 2021 -name=word_highlighting From 239de601784189ee7cff869199ee42aad0ab2716 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 2 Apr 2021 10:11:29 -0700 Subject: [PATCH 0068/1254] Removed uncessary encoding of solr query. --- .../app/rest/iiif/service/SearchService.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index 95e3cffbf5..e6c9414efd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -84,19 +84,18 @@ public class SearchService extends AbstractResourceService { * @return IIIF json */ public String searchWithinManifest(UUID uuid, String query) { - String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); - String json = getSolrSearchResponse(encodedQuery, getManifestId(uuid)); - return getAnnotationList(json, uuid, encodedQuery); + String json = getSolrSearchResponse(query, getManifestId(uuid)); + return getAnnotationList(json, uuid, query); } /** * Executes the Search API solr query. - * @param encodedQuery encoded query terms + * @param query encoded query terms * @param manifestId the iiif manifest id * * @return json query response */ - private String getSolrSearchResponse(String encodedQuery, String manifestId) { + private String getSolrSearchResponse(String query, String manifestId) { String json = ""; String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("iiif.solr.search.url"); @@ -105,7 +104,7 @@ public class SearchService extends AbstractResourceService { HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); solrServer.setBaseURL(solrService); solrServer.setUseMultiPartPost(true); - SolrQuery solrQuery = getSolrQuery(encodedQuery, manifestId); + SolrQuery solrQuery = getSolrQuery(query, manifestId); QueryRequest req = new QueryRequest(solrQuery); req.setResponseParser(new NoOpResponseParser("json")); NamedList resp = null; @@ -125,13 +124,13 @@ public class SearchService extends AbstractResourceService { /** * Constructs a solr search URL. * - * @param encodedQuery the search terms + * @param query the search terms * @param manifestId the id of the manifest in which to search * @return solr query */ - private SolrQuery getSolrQuery(String encodedQuery, String manifestId) { + private SolrQuery getSolrQuery(String query, String manifestId) { SolrQuery solrQuery = new SolrQuery(); - solrQuery.setQuery("ocr_text:" + encodedQuery + + solrQuery.setQuery("ocr_text:" + query + " AND manifest_url:\"" + manifestId + "\""); solrQuery.set(CommonParams.WT, "json"); solrQuery.set("hl", "true"); @@ -161,11 +160,12 @@ public class SearchService extends AbstractResourceService { * * @param json solr search result * @param uuid DSpace Item uuid - * @param encodedQuery the solr query + * @param query the solr query * @return a search response in JSON */ - private String getAnnotationList(String json, UUID uuid, String encodedQuery) { - searchResult.setIdentifier(getManifestId(uuid) + "/search?q=" + encodedQuery); + private String getAnnotationList(String json, UUID uuid, String query) { + searchResult.setIdentifier(getManifestId(uuid) + "/search?q=" + + URLEncoder.encode(query, StandardCharsets.UTF_8)); GsonBuilder builder = new GsonBuilder(); Gson gson = builder.create(); JsonObject body = gson.fromJson(json, JsonObject.class); From 20a9c0a686ed98175b1940f5ad644a56655f97f3 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 2 Apr 2021 14:32:04 -0700 Subject: [PATCH 0069/1254] Updated IT for revised ItemBuilder. --- .../app/rest/iiif/IIIFRestRepositoryIT.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java index 8a88b21f49..895ca28a60 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java @@ -68,7 +68,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withRelationshipType("IIIFSearchable") + .withEntityType("IIIFSearchable") .build(); context.restoreAuthSystemState(); // Status 500 @@ -89,7 +89,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withRelationshipType("IIIFSearchable") + .withEntityType("IIIFSearchable") .build(); String bitstreamContent = "ThisIsSomeDummyText"; @@ -131,7 +131,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withRelationshipType("IIIFSearchable") + .withEntityType("IIIFSearchable") .build(); String bitstreamContent = "ThisIsSomeText"; @@ -176,7 +176,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withRelationshipType("IIIFSearchable") + .withEntityType("IIIFSearchable") .build(); String bitstreamContent = "ThisIsSomeText"; @@ -222,7 +222,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") .withMetadata("dc", "rights", "uri", "https://license.org") - .withRelationshipType("IIIF") + .withEntityType("IIIF") .build(); String bitstreamContent = "ThisIsSomeDummyText"; @@ -283,7 +283,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withRelationshipType("IIIFSearchable") + .withEntityType("IIIFSearchable") .build(); String bitstreamContent = "ThisIsSomeText"; @@ -346,7 +346,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") .withMetadata("dc", "rights", "uri", "https://license.org") - .withRelationshipType("IIIF") + .withEntityType("IIIF") .build(); String bitstreamContent = "ThisIsSomeDummyText"; @@ -384,7 +384,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") .withMetadata("dc", "rights", "uri", "https://license.org") - .withRelationshipType("IIIF") + .withEntityType("IIIF") .build(); String bitstreamContent = "ThisIsSomeDummyText"; @@ -474,7 +474,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withRelationshipType("IIIF") + .withEntityType("IIIF") .build(); String bitstreamContent = "ThisIsSomeDummyText"; @@ -506,7 +506,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withRelationshipType("IIIF") + .withEntityType("IIIF") .build(); String bitstreamContent = "ThisIsSomeDummyText"; @@ -535,7 +535,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withRelationshipType("IIIF") + .withEntityType("IIIF") .build(); String bitstreamContent = "ThisIsSomeDummyText"; From 14ef8ab75bc34070430c066d76e96e4cee5df0aa Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 2 Apr 2021 15:12:28 -0700 Subject: [PATCH 0070/1254] Using dspace.entity.type for iiif bundle lookup. --- .../java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index e41815c445..c9ec4cb796 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -59,7 +59,7 @@ public class IIIFUtils { */ public List getIiifBundle(Item item, String iiifBundle) { boolean iiif = item.getMetadata().stream() - .filter(m -> m.getMetadataField().toString().contentEquals("relationship_type")) + .filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_type")) .anyMatch(m -> m.getValue().contentEquals("IIIF") || m.getValue().contentEquals("IIIFSearchable")); List bundles; @@ -116,7 +116,7 @@ public class IIIFUtils { */ public boolean isSearchable(Item item) { return item.getMetadata().stream() - .filter(m -> m.getMetadataField().toString().contentEquals("relationship_type")) + .filter(m -> m.getMetadataField().toString().contentEquals("dspace.entity.type")) .anyMatch(m -> m.getValue().contentEquals("IIIFSearchable")); } From 8c3673e27e66a3b5a54de0ed09cb4b7c9a865ec2 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 2 Apr 2021 15:20:52 -0700 Subject: [PATCH 0071/1254] Added caching for manifests. --- .../src/main/java/org/dspace/app/rest/Application.java | 2 ++ .../main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java | 2 ++ 2 files changed, 4 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 2f3c69e772..c9e472750b 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 @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; import org.springframework.hateoas.server.LinkRelationProvider; @@ -55,6 +56,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; */ @SpringBootApplication @EnableScheduling +@EnableCaching public class Application extends SpringBootServletInitializer { private static final Logger log = LoggerFactory.getLogger(Application.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java index 1ea10fe24e..72962452a7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java @@ -19,6 +19,7 @@ import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -60,6 +61,7 @@ public class IIIFRestRepository { * @param id DSpace Item uuid * @return manifest as JSON */ + @Cacheable("manifests") @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") public String getManifest(Context context, UUID id) throws ResourceNotFoundException { From 0efb145b8f16547721ca01e5b3b39fd95c816efc Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 2 Apr 2021 18:49:07 -0700 Subject: [PATCH 0072/1254] Corrected dspace.entity.type metadata field name in utils. --- .../java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index c9ec4cb796..c0f3fad3a2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -116,7 +116,7 @@ public class IIIFUtils { */ public boolean isSearchable(Item item) { return item.getMetadata().stream() - .filter(m -> m.getMetadataField().toString().contentEquals("dspace.entity.type")) + .filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_type")) .anyMatch(m -> m.getValue().contentEquals("IIIFSearchable")); } From 348a7d263e983720db65095fff7e76874fe61040 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 4 Apr 2021 11:53:09 -0700 Subject: [PATCH 0073/1254] Corrected multi-word solr queries. --- .../app/rest/iiif/service/SearchService.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index e6c9414efd..776e2f1505 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -27,6 +27,8 @@ import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.impl.NoOpResponseParser; import org.apache.solr.client.solrj.request.QueryRequest; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.util.NamedList; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; @@ -35,6 +37,7 @@ import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.discovery.SolrSearchCore; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -70,6 +73,9 @@ public class SearchService extends AbstractResourceService { @Autowired SearchResultGenerator searchResult; + @Autowired + protected SolrSearchCore solrSearchCore; + public SearchService(ConfigurationService configurationService) { setConfiguration(configurationService); validationEnabled = configurationService @@ -102,12 +108,12 @@ public class SearchService extends AbstractResourceService { UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); if (urlValidator.isValid(solrService) || this.validationEnabled) { HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); - solrServer.setBaseURL(solrService); solrServer.setUseMultiPartPost(true); - SolrQuery solrQuery = getSolrQuery(query, manifestId); + SolrQuery solrQuery = getSolrQuery(adjustQuery(query), manifestId); QueryRequest req = new QueryRequest(solrQuery); + // return raw json response. req.setResponseParser(new NoOpResponseParser("json")); - NamedList resp = null; + NamedList resp; try { resp = solrServer.request(req); json = (String) resp.get("response"); @@ -121,6 +127,18 @@ public class SearchService extends AbstractResourceService { return json; } + /** + * Wraps multi-word queries in parens. + * @param query the search query + * @return + */ + private String adjustQuery(String query) { + if (query.split(" ").length > 1) { + return '(' + query + ')'; + } + return query; + } + /** * Constructs a solr search URL. * @@ -130,8 +148,7 @@ public class SearchService extends AbstractResourceService { */ private SolrQuery getSolrQuery(String query, String manifestId) { SolrQuery solrQuery = new SolrQuery(); - solrQuery.setQuery("ocr_text:" + query + - " AND manifest_url:\"" + manifestId + "\""); + solrQuery.set("q", "ocr_text:" + query + " AND manifest_url:\"" + manifestId + "\""); solrQuery.set(CommonParams.WT, "json"); solrQuery.set("hl", "true"); solrQuery.set("hl.ocr.fl", "ocr_text"); From a82727b552da21811fb0cb9ef4ec2f877ba49981 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 5 Apr 2021 10:16:09 -0700 Subject: [PATCH 0074/1254] Removed unused import. --- .../java/org/dspace/app/rest/iiif/service/SearchService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index 776e2f1505..3331c78b22 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -27,8 +27,6 @@ import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.impl.NoOpResponseParser; import org.apache.solr.client.solrj.request.QueryRequest; -import org.apache.solr.client.solrj.response.QueryResponse; -import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.util.NamedList; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; From 9372c677b6a5c9261680261f0d5f9dc11d82623a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 15 Apr 2021 12:07:39 -0700 Subject: [PATCH 0075/1254] Updated logger. --- .../java/org/dspace/app/rest/iiif/service/CanvasService.java | 4 ++-- .../org/dspace/app/rest/iiif/service/ManifestService.java | 4 ++-- .../java/org/dspace/app/rest/iiif/service/SearchService.java | 4 ++-- .../java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index fe1f30427a..9c7ee3bb91 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.iiif.service; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.services.ConfigurationService; @@ -19,7 +19,7 @@ import org.springframework.stereotype.Component; @Scope("prototype") public class CanvasService extends AbstractResourceService { - private static final Logger log = Logger.getLogger(CanvasService.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CanvasService.class); // Default canvas dimensions. protected static final Integer DEFAULT_CANVAS_WIDTH = 1200; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 671f52073e..cc78779df4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.UUID; import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; @@ -38,7 +38,7 @@ import org.springframework.stereotype.Component; @Component public class ManifestService extends AbstractResourceService { - private static final Logger log = Logger.getLogger(ManifestService.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ManifestService.class); private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; private static final String RELATED_ITEM_LABEL = "DSpace item view"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index 3331c78b22..3e8b8f3008 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -21,7 +21,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.commons.validator.routines.UrlValidator; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrClient; @@ -49,7 +49,7 @@ import org.springframework.web.context.annotation.RequestScope; @RequestScope public class SearchService extends AbstractResourceService { - private static final Logger log = Logger.getLogger(SearchService.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SearchService.class); private final boolean validationEnabled; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index c0f3fad3a2..5aa71a4965 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.ObjectMapperFactory; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.app.rest.iiif.model.info.RangeModel; @@ -36,7 +36,7 @@ import org.springframework.stereotype.Component; @Component public class IIIFUtils { - private static final Logger log = Logger.getLogger(IIIFUtils.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFUtils.class); // The canvas position will be appended to this string. private static final String CANVAS_PATH_BASE = "/canvas/c"; From e4a828a39c73e17c48bc2ab27f4d4b727ab75340 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 17 Apr 2021 13:42:14 -0700 Subject: [PATCH 0076/1254] Trial implementation of manifest caching. Fixed style errors. --- LICENSES_THIRD_PARTY | 2 ++ dspace-server-webapp/pom.xml | 24 +++++++++++-- .../app/rest/iiif/IIIFRestRepository.java | 4 ++- .../app/rest/iiif/cache/CacheLogger.java | 24 +++++++++++++ .../rest/iiif/service/ManifestService.java | 4 +-- .../src/main/resources/application.properties | 7 +++- .../src/main/resources/cache/ehcache.xml | 34 +++++++++++++++++++ 7 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheLogger.java create mode 100644 dspace-server-webapp/src/main/resources/cache/ehcache.xml diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 5307f8ab0c..be3c14444c 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -79,6 +79,8 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * Apache Log4j (log4j:log4j:1.2.17 - http://logging.apache.org/log4j/1.2/) * "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/) * Ehcache Core (net.sf.ehcache:ehcache-core:2.4.3 - http://ehcache.org) + * Ehcache 3 (org.ehcache:ehcache:3.4.0 - https://www.ehcache.org/) + * JSR107 API (javax.cache:cache-api:1.1.0 - https://github.com/jsr107/jsr107spec) * opencsv (net.sf.opencsv:opencsv:2.3 - http://opencsv.sf.net) * Abdera Client (org.apache.abdera:abdera-client:1.1.3 - http://abdera.apache.org/abdera-client) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 3b55e39fe2..216bf79466 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -474,14 +474,34 @@ + + + org.springframework.boot + spring-boot-starter-cache + ${spring-boot.version} + + + + javax.cache + cache-api + 1.1.0 + + + + org.ehcache + ehcache + 3.4.0 + + de.digitalcollections.iiif iiif-apis 0.3.7 - org.reflections - reflections + org.javassist + javassist com.fasterxml.jackson.datatype diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java index 72962452a7..1897189d92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java @@ -48,6 +48,7 @@ public class IIIFRestRepository { @Autowired CanvasLookupService canvasLookupService; + /** * The manifest response contains sufficient information for the client to initialize itself * and begin to display something quickly to the user. The manifest resource represents a single @@ -61,7 +62,7 @@ public class IIIFRestRepository { * @param id DSpace Item uuid * @return manifest as JSON */ - @Cacheable("manifests") + @Cacheable(key = "#id.toString()", cacheNames = "manifests") @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") public String getManifest(Context context, UUID id) throws ResourceNotFoundException { @@ -128,4 +129,5 @@ public class IIIFRestRepository { public String getSeeAlsoAnnotations(Context context, UUID id) { return annotationListService.getSeeAlsoAnnotations(context, id); } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheLogger.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheLogger.java new file mode 100644 index 0000000000..c3cd9c188d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheLogger.java @@ -0,0 +1,24 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.cache; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.ehcache.event.CacheEvent; +import org.ehcache.event.CacheEventListener; + +public class CacheLogger implements CacheEventListener { + private static final Logger log = LogManager.getLogger(CacheLogger.class); + + @Override + public void onEvent(CacheEvent cacheEvent) { + log.info("Cache Event Type: {} | Manifest Key: {} ", + cacheEvent.getType(), cacheEvent.getKey()); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index cc78779df4..a0ffe11d15 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -84,7 +84,7 @@ public class ManifestService extends AbstractResourceService { * @return Manifest as JSON */ public String getManifest(Item item, Context context) { - initializeManifestGenerator(item, context); + initializeManifest(item, context); return utils.asJson(manifestGenerator.getResource()); } @@ -95,7 +95,7 @@ public class ManifestService extends AbstractResourceService { * @param context DSpace context * @return manifest object */ - private void initializeManifestGenerator(Item item, Context context) { + private void initializeManifest(Item item, Context context) { List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); List bitstreams = utils.getBitstreams(bundles); Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); diff --git a/dspace-server-webapp/src/main/resources/application.properties b/dspace-server-webapp/src/main/resources/application.properties index 2b8463e18a..f90a8bc6aa 100644 --- a/dspace-server-webapp/src/main/resources/application.properties +++ b/dspace-server-webapp/src/main/resources/application.properties @@ -77,6 +77,12 @@ spring.http.encoding.force=true # However, you may wish to set this to "always" in your 'local.cfg' for development or debugging purposes. server.error.include-stacktrace = never +###################### +# Cache Properties +# Added for IIIF support. +# Path to configuration +spring.cache.jcache.config=classpath:cache/ehcache.xml + ###################### # Spring Boot Autoconfigure # @@ -102,7 +108,6 @@ spring.main.allow-bean-definition-overriding = true #logging.level.org.springframework.boot=DEBUG #logging.level.org.springframework.web=DEBUG #logging.level.org.hibernate=ERROR - ######################### # Log4J configuration logging.config = ${dspace.dir}/config/log4j2.xml diff --git a/dspace-server-webapp/src/main/resources/cache/ehcache.xml b/dspace-server-webapp/src/main/resources/cache/ehcache.xml new file mode 100644 index 0000000000..13f9b398b4 --- /dev/null +++ b/dspace-server-webapp/src/main/resources/cache/ehcache.xml @@ -0,0 +1,34 @@ + + + + + + org.dspace.app.rest.iiif.cache.CacheLogger + ASYNCHRONOUS + UNORDERED + CREATED + EXPIRED + EVICTED + + + + 1000 + 10 + + + + + From 62a815b92b5bbdd453b929420187d6d43872666c Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 4 May 2021 16:48:52 -0700 Subject: [PATCH 0077/1254] Changed incorrect inheritance. --- .../org/dspace/app/rest/IIIFController.java | 19 +++++++------------ .../app/rest/iiif/IIIFRestRepository.java | 15 ++++++++------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java index 64046f883a..9ca6db00b9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java @@ -10,8 +10,6 @@ package org.dspace.app.rest; import java.util.UUID; import org.dspace.app.rest.iiif.IIIFRestRepository; -import org.dspace.app.rest.repository.AbstractDSpaceRestRepository; -import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -24,7 +22,7 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping("/api/iiif") -public class IIIFController extends AbstractDSpaceRestRepository { +public class IIIFController { @Autowired IIIFRestRepository iiifRestRepository; @@ -44,8 +42,7 @@ public class IIIFController extends AbstractDSpaceRestRepository { */ @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest") public String findOne(@PathVariable UUID id) { - Context context = obtainContext(); - return iiifRestRepository.getManifest(context, id); + return iiifRestRepository.getManifest(id); } /** @@ -61,13 +58,13 @@ public class IIIFController extends AbstractDSpaceRestRepository { * This endpoint for searches within the manifest scope (by DSpace item uuid). * * @param id DSpace Item uuid - * @param q query terms + * @param query query terms * @return AnnotationList as JSON */ @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest/search") public String searchInManifest(@PathVariable UUID id, - @RequestParam(name = "q") String q) { - return iiifRestRepository.searchInManifest(id, q); + @RequestParam(name = "q") String query) { + return iiifRestRepository.searchInManifest(id, query); } /** @@ -83,8 +80,7 @@ public class IIIFController extends AbstractDSpaceRestRepository { */ @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest/seeAlso") public String findSeeAlsoList(@PathVariable UUID id) { - Context context = obtainContext(); - return iiifRestRepository.getSeeAlsoAnnotations(context, id); + return iiifRestRepository.getSeeAlsoAnnotations(id); } /** @@ -101,7 +97,6 @@ public class IIIFController extends AbstractDSpaceRestRepository { */ @RequestMapping(method = RequestMethod.GET, value = "/{id}/canvas/{cid}") public String findCanvas(@PathVariable UUID id, @PathVariable String cid) { - Context context = obtainContext(); - return iiifRestRepository.getCanvas(context, id, cid); + return iiifRestRepository.getCanvas(id, cid); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java index 1897189d92..b8c8eb381e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFRestRepository.java @@ -14,6 +14,7 @@ import org.dspace.app.rest.iiif.service.AnnotationListService; import org.dspace.app.rest.iiif.service.CanvasLookupService; import org.dspace.app.rest.iiif.service.ManifestService; import org.dspace.app.rest.iiif.service.SearchService; +import org.dspace.app.rest.repository.AbstractDSpaceRestRepository; import org.dspace.content.Item; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; @@ -28,7 +29,7 @@ import org.springframework.stereotype.Component; * Repository for IIIF Presentation and Search API requests. */ @Component -public class IIIFRestRepository { +public class IIIFRestRepository extends AbstractDSpaceRestRepository { @Autowired ItemService itemService; @@ -58,15 +59,15 @@ public class IIIFRestRepository { * * Returns manifest for single DSpace item. * - * @param context DSpace context * @param id DSpace Item uuid * @return manifest as JSON */ @Cacheable(key = "#id.toString()", cacheNames = "manifests") @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") - public String getManifest(Context context, UUID id) + public String getManifest(UUID id) throws ResourceNotFoundException { Item item; + Context context = obtainContext(); try { item = itemService.find(context, id); } catch (SQLException e) { @@ -83,15 +84,15 @@ public class IIIFRestRepository { * laying out the different content resources that make up the display. This information * should be embedded within a sequence. * - * @param context DSpace context * @param id DSpace item uuid * @param canvasId canvas identifier * @return canvas as JSON */ @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") - public String getCanvas(Context context, UUID id, String canvasId) + public String getCanvas(UUID id, String canvasId) throws ResourceNotFoundException { Item item; + Context context = obtainContext(); try { item = itemService.find(context, id); } catch (SQLException e) { @@ -121,12 +122,12 @@ public class IIIFRestRepository { /** * Returns annotations for machine readable metadata that describes the resource. * - * @param context DSpace context * @param id the Item uuid * @return AnnotationList as JSON */ @PreAuthorize("hasPermission(#id, 'ITEM', 'READ')") - public String getSeeAlsoAnnotations(Context context, UUID id) { + public String getSeeAlsoAnnotations(UUID id) { + Context context = obtainContext(); return annotationListService.getSeeAlsoAnnotations(context, id); } From e226ba954ba5ce17c5ea519e99417719ad6c0a12 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 4 May 2021 19:30:31 -0700 Subject: [PATCH 0078/1254] Correction to annotationlist generator. --- .../generator/AnnotationListGenerator.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java index 4c76b4131c..6b51e3775b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest.iiif.model.generator; +import java.util.ArrayList; +import java.util.List; + import de.digitalcollections.iiif.model.openannotation.Annotation; import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; import de.digitalcollections.iiif.model.sharedcanvas.Resource; @@ -24,20 +27,24 @@ import org.springframework.stereotype.Component; public class AnnotationListGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { private String identifier; - private Annotation annotation; + private List annotations = new ArrayList<>(); public void setIdentifier(String identifier) { this.identifier = identifier; } + /** + * Adds Annotation resource to the annotation list. + * @param annotation the Annotation Resource + */ public void addResource(org.dspace.app.rest.iiif.model.generator.AnnotationGenerator annotation) { - this.annotation = (Annotation) annotation.getResource(); + this.annotations.add((Annotation) annotation.getResource()); } @Override public Resource getResource() { - AnnotationList annotations = new AnnotationList(identifier); - annotations.addResource(annotation); - return annotations; + AnnotationList annotationList = new AnnotationList(identifier); + annotationList.setResources(annotations); + return annotationList; } } From 77bed7571c3aabfd83c2912407ec27f48d97547f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 16 May 2021 10:28:15 -0700 Subject: [PATCH 0079/1254] Added trackPages parameter to solr query that was introduced in solr-ocrhighlighing v 0.6.0. This version was released 5 days ago and is not yet available via Maven. --- .../java/org/dspace/app/rest/iiif/service/SearchService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index 3e8b8f3008..b26e76e24c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -153,6 +153,7 @@ public class SearchService extends AbstractResourceService { solrQuery.set("hl.ocr.contextBlock", "line"); solrQuery.set("hl.ocr.contextSize", "2"); solrQuery.set("hl.snippets", "10"); + solrQuery.set("hl.ocr.trackPages", "off"); solrQuery.set("hl.ocr.limitBlock","page"); solrQuery.set("hl.ocr.absoluteHighlights", "true"); From d02827019b3e7059b6be16c5043cd89a2445e00d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 24 Jun 2021 13:46:01 -0400 Subject: [PATCH 0080/1254] [DS-4478] Ported from 6_x to main. --- dspace-api/pom.xml | 8 + .../org/dspace/app/util/Configuration.java | 29 ++- .../org/dspace/app/util/ConfigurationIT.java | 232 ++++++++++++++++++ 3 files changed, 264 insertions(+), 5 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index e3e84cccd7..835c61e92a 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -889,6 +889,14 @@ 20180130 + + + com.github.stefanbirkner + system-rules + 1.19.0 + test + + com.opencsv diff --git a/dspace-api/src/main/java/org/dspace/app/util/Configuration.java b/dspace-api/src/main/java/org/dspace/app/util/Configuration.java index e9b125c41c..3a38daf7e7 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/Configuration.java +++ b/dspace-api/src/main/java/org/dspace/app/util/Configuration.java @@ -51,6 +51,8 @@ public class Configuration { "optional name of the module in which 'property' exists"); options.addOption("r", "raw", false, "do not do property substitution on the value"); + options.addOption("a", "all", false, + "display all values of an array property"); options.addOption("?", "Get help"); options.addOption("h", "help", false, "Get help"); @@ -90,19 +92,36 @@ public class Configuration { propNameBuilder.append(cmd.getOptionValue('p')); String propName = propNameBuilder.toString(); - // Print the property's value, if it exists + // Print the property's value(s), if it exists ConfigurationService cfg = DSpaceServicesFactory.getInstance().getConfigurationService(); if (!cfg.hasProperty(propName)) { System.out.println(); } else { - String val; if (cmd.hasOption('r')) { - val = cfg.getPropertyValue(propName).toString(); + // Print "raw" values (without property substitutions) + Object rawValue = cfg.getPropertyValue(propName); + if (rawValue.getClass().isArray()) { + for (Object value : (Object[]) rawValue) { + System.out.println(value.toString()); + if (!cmd.hasOption('a')) { + break; // Unless --all print only one value + } + } + } else { // Not an array + System.out.println(rawValue.toString()); + } } else { - val = cfg.getProperty(propName); + // Print values with property substitutions + String[] values = cfg.getArrayProperty(propName); + for (String value : values) { + System.out.println(value); + if (!cmd.hasOption('a')) { + break; // Unless --all print only one value + } + } } - System.out.println(val); } + System.exit(0); } } diff --git a/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java b/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java new file mode 100644 index 0000000000..8f18c9754e --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java @@ -0,0 +1,232 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.collection.IsArrayContainingInAnyOrder.arrayContainingInAnyOrder; + +import org.dspace.AbstractDSpaceTest; +import org.dspace.services.ConfigurationService; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.Assertion; +import org.junit.contrib.java.lang.system.ExpectedSystemExit; +import org.junit.contrib.java.lang.system.SystemErrRule; +import org.junit.contrib.java.lang.system.SystemOutRule; + +/** + * Tests for configuration utilities. + * + * Because our command-line tools call System.exit(), we can't expect any code + * (such as assertions) following the call to main() to be executed. Instead we + * set up expectations in advance and attach them to an exit() trapper. + * + * @author mhwood + */ +public class ConfigurationIT + extends AbstractDSpaceTest { + + private static ConfigurationService cfg; + + private static final String SINGLE_PROPERTY = "test.single"; + private static final String SINGLE_VALUE = "value"; + + private static final String ARRAY_PROPERTY = "test.array"; + private static final String[] ARRAY_VALUE = { "one", "two" }; + + private static final String PLACEHOLDER_PROPERTY = "test.substituted"; + private static final String PLACEHOLDER_VALUE = "insert ${test.single} here"; // Keep aligned with SINGLE_NAME + private static final String SUBSTITUTED_VALUE = "insert value here"; // Keep aligned with SINGLE_VALUE + + private static final String MISSING_PROPERTY = "test.missing"; + + /** Capture standard output. */ + @Rule + public final SystemOutRule systemOutRule = new SystemOutRule(); + + /** Capture standard error. */ + @Rule + public final SystemErrRule systemErrRule = new SystemErrRule(); + + /** Capture System.exit() value. */ + @Rule + public final ExpectedSystemExit expectedSystemExit = ExpectedSystemExit.none(); + + /** + * Create some expected properties before all tests. + */ + @BeforeClass + public static void setupSuite() { + cfg = kernelImpl.getConfigurationService(); + + cfg.setProperty(SINGLE_PROPERTY, SINGLE_VALUE); + cfg.setProperty(ARRAY_PROPERTY, ARRAY_VALUE); + cfg.setProperty(PLACEHOLDER_PROPERTY, PLACEHOLDER_VALUE); + cfg.setProperty(MISSING_PROPERTY, null); // Ensure that this one is undefined + } + + /** + * After all tests, remove the properties that were created at entry. + */ + @AfterClass + public static void teardownSuite() { + if (null != cfg) { + cfg.setProperty(SINGLE_PROPERTY, null); + cfg.setProperty(ARRAY_PROPERTY, null); + cfg.setProperty(PLACEHOLDER_PROPERTY, null); + } + } + + /** + * Test fetching all values of a single-valued property. + */ + @Test + public void testMainAllSingle() { + String[] argv; + argv = new String[] { + "--all", + "--property", SINGLE_PROPERTY + }; + expectedSystemExit.expectSystemExitWithStatus(0); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String[] output = systemOutRule.getLogWithNormalizedLineSeparator() + .split("\n"); + assertThat(output, arrayWithSize(1)); + } + }); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String[] output = systemOutRule.getLogWithNormalizedLineSeparator() + .split("\n"); + assertThat(output[0], equalTo(SINGLE_VALUE)); + } + }); + systemOutRule.enableLog(); + Configuration.main(argv); + } + + /** + * Test fetching all values of an array property. + */ + @Test + public void testMainAllArray() { + String[] argv; + argv = new String[] { + "--all", + "--property", ARRAY_PROPERTY + }; + expectedSystemExit.expectSystemExitWithStatus(0); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String[] output = systemOutRule.getLogWithNormalizedLineSeparator() + .split("\n"); + assertThat(output, arrayWithSize(ARRAY_VALUE.length)); + } + }); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String[] output = systemOutRule.getLogWithNormalizedLineSeparator() + .split("\n"); + assertThat(output, arrayContainingInAnyOrder(ARRAY_VALUE)); + } + }); + systemOutRule.enableLog(); + Configuration.main(argv); + } + + /** + * Test fetching all values of a single-valued property containing property + * placeholders. + */ + @Test + public void testMainAllSubstitution() { + String[] argv; + argv = new String[] { + "--all", + "--property", PLACEHOLDER_PROPERTY + }; + expectedSystemExit.expectSystemExitWithStatus(0); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String[] output = systemOutRule.getLogWithNormalizedLineSeparator() + .split("\n"); + assertThat(output, arrayWithSize(1)); + } + }); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String[] output = systemOutRule.getLogWithNormalizedLineSeparator() + .split("\n"); + assertThat(output[0], equalTo(SUBSTITUTED_VALUE)); + } + }); + systemOutRule.enableLog(); + Configuration.main(argv); + } + + /** + * Test fetching all values of a single-valued property containing property + * placeholders, suppressing property substitution. + */ + @Test + public void testMainAllRaw() { + // Can it handle a raw property (with substitution placeholders)? + String[] argv; + argv = new String[] { + "--all", + "--property", PLACEHOLDER_PROPERTY, + "--raw" + }; + expectedSystemExit.expectSystemExitWithStatus(0); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String[] output = systemOutRule.getLogWithNormalizedLineSeparator() + .split("\n"); + assertThat(output, arrayWithSize(1)); + } + }); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String[] output = systemOutRule.getLogWithNormalizedLineSeparator() + .split("\n"); + assertThat(output[0], equalTo(PLACEHOLDER_VALUE)); + } + }); + systemOutRule.enableLog(); + Configuration.main(argv); + } + + /** + * Test fetching all values of an undefined property. + */ + @Test + public void testMainAllUndefined() { + // Can it handle an undefined property? + String[] argv; + argv = new String[] { + "--all", + "--property", MISSING_PROPERTY + }; + expectedSystemExit.expectSystemExitWithStatus(0); + expectedSystemExit.checkAssertionAfterwards(new Assertion() { + @Override public void checkAssertion() { + String outputs = systemOutRule.getLogWithNormalizedLineSeparator(); + String[] output = outputs.split("\n"); + assertThat(output, arrayWithSize(0)); // Huh? Shouldn't split() return { "" } ? + } + }); + systemOutRule.enableLog(); + Configuration.main(argv); + } +} From 0a01826c4e01e83c8b25473ed495b0a1d7a3bff7 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 11 Jul 2021 06:50:17 -0700 Subject: [PATCH 0081/1254] Updated cache config and renamed classes. --- .../src/main/java/org/dspace/app/rest/Application.java | 1 - .../org/dspace/app/rest/iiif/cache/CacheConfig.java | 10 ++++++++++ .../info/{AnnotationModel.java => Annotation.java} | 0 .../iiif/model/info/{CanvasModel.java => Canvas.java} | 0 .../iiif/model/info/{RangeModel.java => Range.java} | 0 .../src/main/resources/{ => iiif}/cache/ehcache.xml | 0 6 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/{AnnotationModel.java => Annotation.java} (100%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/{CanvasModel.java => Canvas.java} (100%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/{RangeModel.java => Range.java} (100%) rename dspace-server-webapp/src/main/resources/{ => iiif}/cache/ehcache.xml (100%) 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 c9e472750b..7a430afde1 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 @@ -56,7 +56,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; */ @SpringBootApplication @EnableScheduling -@EnableCaching public class Application extends SpringBootServletInitializer { private static final Logger log = LoggerFactory.getLogger(Application.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java new file mode 100644 index 0000000000..a6bde31bba --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java @@ -0,0 +1,10 @@ +package org.dspace.app.rest.iiif.cache; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +public class CacheConfig { + // Spring boot cache configuration. +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java similarity index 100% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/AnnotationModel.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java similarity index 100% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/CanvasModel.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java similarity index 100% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/RangeModel.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java diff --git a/dspace-server-webapp/src/main/resources/cache/ehcache.xml b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml similarity index 100% rename from dspace-server-webapp/src/main/resources/cache/ehcache.xml rename to dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml From cbcf8c305ea301e54bd07afbb2e53521856f6a20 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 11 Jul 2021 06:52:16 -0700 Subject: [PATCH 0082/1254] Updated cache path. --- .../src/main/resources/application.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/resources/application.properties b/dspace-server-webapp/src/main/resources/application.properties index f90a8bc6aa..0f7d811ebb 100644 --- a/dspace-server-webapp/src/main/resources/application.properties +++ b/dspace-server-webapp/src/main/resources/application.properties @@ -79,9 +79,9 @@ server.error.include-stacktrace = never ###################### # Cache Properties -# Added for IIIF support. -# Path to configuration -spring.cache.jcache.config=classpath:cache/ehcache.xml +# Added for IIIF cache support. +# Path to configuration file. +spring.cache.jcache.config=classpath:iiif/cache/ehcache.xml ###################### # Spring Boot Autoconfigure From a203d942ee4a4105ad492e6d9a1f99081e61e20f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 11 Jul 2021 06:53:00 -0700 Subject: [PATCH 0083/1254] Updated info models. --- .../app/rest/iiif/model/ObjectMapperFactory.java | 8 ++++++++ .../dspace/app/rest/iiif/model/info/Annotation.java | 2 +- .../org/dspace/app/rest/iiif/model/info/Canvas.java | 2 +- .../org/dspace/app/rest/iiif/model/info/Info.java | 12 ++++++------ .../org/dspace/app/rest/iiif/model/info/Range.java | 2 +- .../app/rest/iiif/service/ManifestService.java | 6 +++--- .../dspace/app/rest/iiif/service/util/IIIFUtils.java | 4 ++-- 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java index 1b5bb782c0..a5d8aa63ae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java @@ -16,10 +16,18 @@ public class ObjectMapperFactory { private ObjectMapperFactory() {} + /** + * Gets the jackson ObjectMapper with iiif configuration. + * @return + */ public static ObjectMapper getIiifObjectMapper() { return new IiifObjectMapper(); } + /** + * Gets the jackson SimpleModule with iiif configuration. + * @return + */ public static SimpleModule getIiifModule() { return new IiifModule(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java index 3bfcf5efca..800c85863f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.iiif.model.info; -public class AnnotationModel { +public class Annotation { private String motivation; private String id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java index 8dc6cac0ca..f3d260c92a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.iiif.model.info; -public class CanvasModel { +public class Canvas { private String label; private int width; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java index 898576639f..dcebadfe2c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java @@ -11,8 +11,8 @@ import java.util.List; public class Info { - private List canvases; - private List structures; + private List canvases; + private List structures; private GlobalDefaults globalDefaults; public GlobalDefaults getGlobalDefaults() { @@ -23,19 +23,19 @@ public class Info { this.globalDefaults = globalDefaults; } - public void setCanvases(List canvases) { + public void setCanvases(List canvases) { this.canvases = canvases; } - public List getCanvases() { + public List getCanvases() { return this.canvases; } - public void setStructures(List structures) { + public void setStructures(List structures) { this.structures = structures; } - public List getStructures() { + public List getStructures() { return structures; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java index b1ef0503bf..45e819c57d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.iiif.model.info; -public class RangeModel { +public class Range { private String label; private int start; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index a0ffe11d15..ccd980633d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -20,7 +20,7 @@ import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; import org.dspace.app.rest.iiif.model.generator.RangeGenerator; import org.dspace.app.rest.iiif.model.info.Info; -import org.dspace.app.rest.iiif.model.info.RangeModel; +import org.dspace.app.rest.iiif.model.info.Range; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -299,7 +299,7 @@ public class ManifestService extends AbstractResourceService { * @param identifier */ private void addRanges(Info info, String identifier) { - List rangesFromConfig = utils.getRangesFromInfoObject(info); + List rangesFromConfig = utils.getRangesFromInfoObject(info); if (rangesFromConfig != null) { for (int pos = 0; pos < rangesFromConfig.size(); pos++) { setRange(identifier, rangesFromConfig.get(pos), pos); @@ -314,7 +314,7 @@ public class ManifestService extends AbstractResourceService { * @param range range from info.json configuration * @param pos list position of the range */ - private void setRange(String identifier, RangeModel range, int pos) { + private void setRange(String identifier, Range range, int pos) { String id = IIIF_ENDPOINT + identifier + "/r" + pos; String label = range.getLabel(); rangeGenerator.setIdentifier(id); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 5aa71a4965..2dfe7dc49e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -21,7 +21,7 @@ import de.digitalcollections.iiif.model.sharedcanvas.Resource; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.ObjectMapperFactory; import org.dspace.app.rest.iiif.model.info.Info; -import org.dspace.app.rest.iiif.model.info.RangeModel; +import org.dspace.app.rest.iiif.model.info.Range; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; @@ -192,7 +192,7 @@ public class IIIFUtils { * @param info the parameters model * @return list of range models */ - public List getRangesFromInfoObject(Info info) { + public List getRangesFromInfoObject(Info info) { if (info != null) { return info.getStructures(); } From 44de2ec5a30a0539547c6c807f17a455436f4591 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 11 Jul 2021 06:54:03 -0700 Subject: [PATCH 0084/1254] Added IIIF bundle to solr stats. --- dspace/config/modules/solr-statistics.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/modules/solr-statistics.cfg b/dspace/config/modules/solr-statistics.cfg index 5b67cd4799..ab0e9b86ba 100644 --- a/dspace/config/modules/solr-statistics.cfg +++ b/dspace/config/modules/solr-statistics.cfg @@ -13,7 +13,7 @@ solr-statistics.server = ${solr.server}/statistics # A comma-separated list that contains the bundles for which the bitstreams will be displayed -solr-statistics.query.filter.bundles=ORIGINAL +solr-statistics.query.filter.bundles=ORIGINAL, IIIF # Name of the "configset" (bundle of template core files) which will be used to # create new Solr cores when sharding the statistics data. From 713469cd8095bf64fa3805a7d33a85392e4cf685 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 12 Jul 2021 12:44:10 +1200 Subject: [PATCH 0085/1254] [DS-4522] Removed operator test null item references and comments as per review --- .../content/logic/LogicalFilterTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java index 593546ec8b..25d1236846 100644 --- a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java +++ b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java @@ -220,7 +220,7 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testAndOperator() { // Blank operator And and = new And(); - // Try tests - the item can be null, as the statements are simply returning booleans themselves + // Try tests try { // Set to True, True (expect True) and.setStatements(trueStatements); @@ -247,20 +247,20 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testOrOperator() { // Blank operator Or or = new Or(); - // Try tests - the item can be null, as the statements are simply returning booleans themselves + // Try tests try { // Set to True, True (expect True) or.setStatements(trueStatements); assertTrue("OR operator did not return true for a list of true statements", - or.getResult(context, null)); + or.getResult(context, itemOne)); // Set to True, False (expect True) or.setStatements(trueFalseStatements); assertTrue("OR operator did not return true for a list of statements with at least one false", - or.getResult(context, null)); + or.getResult(context, itemOne)); // Set to False, False (expect False) or.setStatements(falseStatements); assertFalse("OR operator did not return false for a list of false statements", - or.getResult(context, null)); + or.getResult(context, itemOne)); } catch (LogicalStatementException e) { log.error(e.getMessage()); fail("LogicalStatementException thrown testing the OR operator" + e.getMessage()); @@ -274,20 +274,20 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testNandOperator() { // Blank operator Nand nand = new Nand(); - // Try tests - the item can be null, as the statements are simply returning booleans themselves + // Try tests try { // Set to True, True (expect False) nand.setStatements(trueStatements); assertFalse("NAND operator did not return false for a list of true statements", - nand.getResult(context, null)); + nand.getResult(context, itemOne)); // Set to True, False (expect True) nand.setStatements(trueFalseStatements); assertTrue("NAND operator did not return true for a list of statements with at least one false", - nand.getResult(context, null)); + nand.getResult(context, itemOne)); // Set to False, False (expect True) nand.setStatements(falseStatements); assertTrue("NAND operator did not return true for a list of false statements", - nand.getResult(context, null)); + nand.getResult(context, itemOne)); } catch (LogicalStatementException e) { log.error(e.getMessage()); fail("LogicalStatementException thrown testing the NAND operator" + e.getMessage()); @@ -301,20 +301,20 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testNorOperator() { // Blank operator Nor nor = new Nor(); - // Try tests - the item can be null, as the statements are simply returning booleans themselves + // Try tests try { // Set to True, True (expect False) nor.setStatements(trueStatements); assertFalse("NOR operator did not return false for a list of true statements", - nor.getResult(context, null)); + nor.getResult(context, itemOne)); // Set to True, False (expect False) nor.setStatements(trueFalseStatements); assertFalse("NOR operator did not return false for a list of statements with a true and a false", - nor.getResult(context, null)); + nor.getResult(context, itemOne)); // Set to False, False (expect True) nor.setStatements(falseStatements); assertTrue("NOR operator did not return true for a list of false statements", - nor.getResult(context, null)); + nor.getResult(context, itemOne)); } catch (LogicalStatementException e) { log.error(e.getMessage()); fail("LogicalStatementException thrown testing the NOR operator" + e.getMessage()); @@ -328,16 +328,16 @@ public class LogicalFilterTest extends AbstractUnitTest { public void testNotOperator() { // Blank operator Not not = new Not(); - // Try tests - the item can be null, as the statements are simply returning booleans themselves + // Try tests try { // Set to True (expect False) not.setStatements(trueStatementOne); assertFalse("NOT operator did not return false for a true statement", - not.getResult(context, null)); + not.getResult(context, itemOne)); // Set to False (expect True) not.setStatements(falseStatementOne); assertTrue("NOT operator did not return true for a false statement", - not.getResult(context, null)); + not.getResult(context, itemOne)); } catch (LogicalStatementException e) { log.error(e.getMessage()); fail("LogicalStatementException thrown testing the NOT operator" + e.getMessage()); From 2e6552c0c9a4bbc5e9fde3fda27761bf23e7be7c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 12 Jul 2021 13:05:19 +1200 Subject: [PATCH 0086/1254] [DS-4522] Refactor skipFilter Boolean -> boolean (primitive) as per review --- .../identifier/DOIIdentifierProvider.java | 24 +++++++++---------- .../FilteredIdentifierProvider.java | 10 ++++---- .../dspace/identifier/doi/DOIOrganiser.java | 6 ++--- 3 files changed, 20 insertions(+), 20 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 3ffbde93e4..4dbfcf0eae 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -229,7 +229,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @throws IdentifierException */ @Override - public String register(Context context, DSpaceObject dso, Boolean skipFilter) + public String register(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException { if (!(dso instanceof Item)) { // DOI are currently assigned only to Item @@ -253,7 +253,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @throws IdentifierException */ @Override - public void register(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + public void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter) throws IdentifierException { if (!(dso instanceof Item)) { // DOI are currently assigned only to Item @@ -322,7 +322,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @throws SQLException */ @Override - public void reserve(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + public void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter) throws IdentifierException, IllegalArgumentException { String doi = doiService.formatIdentifier(identifier); DOI doiRow = null; @@ -371,7 +371,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @throws IllegalArgumentException * @throws SQLException */ - public void reserveOnline(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + public void reserveOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter) throws IdentifierException, IllegalArgumentException, SQLException { String doi = doiService.formatIdentifier(identifier); // get TableRow and ensure DOI belongs to dso regarding our db @@ -414,9 +414,9 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @throws IllegalArgumentException * @throws SQLException */ - public void registerOnline(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + public void registerOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter) throws IdentifierException, IllegalArgumentException, SQLException { - log.debug("registerOnline: skipFilter is " + skipFilter.toString()); + log.debug("registerOnline: skipFilter is " + skipFilter); String doi = doiService.formatIdentifier(identifier); // get TableRow and ensure DOI belongs to dso regarding our db @@ -470,7 +470,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { String doi = doiService.formatIdentifier(identifier); - Boolean skipFilter = false; + boolean skipFilter = false; if (doiService.findDOIByDSpaceObject(context, dso) != null) { // We can skip the filter here since we know the DOI already exists for the item @@ -576,12 +576,12 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * 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 skipFilter - boolean indicating whether to skip any filtering of items before minting. * @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, boolean skipFilter) throws IdentifierException { String doi = null; try { @@ -910,7 +910,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @throws SQLException * @throws DOIIdentifierException */ - protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, Boolean skipFilter) + protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, boolean skipFilter) throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { DOI doi = null; @@ -1105,8 +1105,8 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { if (this.filterService != null && contentServiceFactory .getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) { try { - Boolean result = filterService.getResult(context, (Item) dso); - log.debug("Result of filter for " + dso.getHandle() + " is " + result.toString()); + boolean result = filterService.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"); 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 73d7f86641..e5f222ff29 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java @@ -43,7 +43,7 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider { * @return identifier * @throws IdentifierException */ - public abstract String register(Context context, DSpaceObject dso, Boolean skipFilter) + public abstract String register(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException; /** @@ -54,7 +54,7 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider { * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration * @throws IdentifierException */ - public abstract void register(Context context, DSpaceObject dso, String identifier, Boolean skipFilter) + public abstract void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter) throws IdentifierException; /** @@ -67,18 +67,18 @@ public abstract class FilteredIdentifierProvider extends IdentifierProvider { * @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, boolean skipFilter) 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 skipFilter - boolean indicating whether to skip any filtering of items before minting. * @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, boolean skipFilter) throws IdentifierException; /** * Check configured item filters to see if this identifier is allowed to be minted 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 a583281abb..e0e0da9440 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 @@ -61,7 +61,7 @@ public class DOIOrganiser { protected ItemService itemService; protected DOIService doiService; protected ConfigurationService configurationService; - protected Boolean skipFilter; + protected boolean skipFilter; /** * Constructor to be called within the main() method @@ -398,7 +398,7 @@ public class DOIOrganiser { * @throws SQLException * @throws DOIIdentifierException */ - public void register(DOI doiRow, Boolean skipFilter) throws SQLException, DOIIdentifierException { + public void register(DOI doiRow, boolean skipFilter) throws SQLException, DOIIdentifierException { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only."); @@ -497,7 +497,7 @@ public class DOIOrganiser { * @throws SQLException * @throws DOIIdentifierException */ - public void reserve(DOI doiRow, Boolean skipFilter) { + public void reserve(DOI doiRow, boolean skipFilter) { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only."); From 4d33fb706caaf60ec076135b58eb376a04275040 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 12 Jul 2021 13:19:15 +1200 Subject: [PATCH 0087/1254] [DS-4522] Add a metadata match test to test for field that item doesn't contain --- .../dspace/content/logic/LogicalFilterTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java index 25d1236846..5a82700b83 100644 --- a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java +++ b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java @@ -14,6 +14,7 @@ import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.sql.Array; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; @@ -419,6 +420,14 @@ public class LogicalFilterTest extends AbstractUnitTest { patterns.add("(?i)yes$"); // Add the list of possible patterns parameters.put("patterns", patterns); + + // Alternate parameters to test for a field where the item has no values + Map missingParameters = new HashMap<>(); + // Match on the dc.subject field - none of our test items have this field set + missingParameters.put("field", "dc.subject"); + // Add a pattern to the missing parameters + missingParameters.put("patterns", new ArrayList<>().add("TEST")); + // Set up condition with these parameters and add it as the sole statement to the metadata filter try { condition.setParameters(parameters); @@ -432,6 +441,12 @@ public class LogicalFilterTest extends AbstractUnitTest { // Test the filter on the third item - expected outcome is false assertFalse("itemThree unexpectedly matched the " + "'dc.title starts with TEST or ends with yes' test", filter.getResult(context, itemThree)); + // Set condition and filter to use the missing field instead + condition.setParameters(missingParameters); + filter.setStatement(condition); + // Test this updated filter against the first item - expected outcome is false + assertFalse("itemOne unexpectedly matched the 'dc.subject contains TEST' test" + + "(it has no dc.subject metadata value)", filter.getResult(context, itemOne)); } catch (LogicalStatementException e) { log.error(e.getMessage()); fail("LogicalStatementException thrown testing the MetadataValuesMatchCondition filter" + e.getMessage()); From 75c93601f88a1d3cca139eccab45bdb36bba8cac Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 12 Jul 2021 13:38:18 +1200 Subject: [PATCH 0088/1254] [DS-4522] Clean up unused import --- .../test/java/org/dspace/content/logic/LogicalFilterTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java index 5a82700b83..7c8268a03b 100644 --- a/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java +++ b/dspace-api/src/test/java/org/dspace/content/logic/LogicalFilterTest.java @@ -14,7 +14,6 @@ import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.sql.Array; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; From a1a77b5492992324b63cf3951a8f0181551e8b6f Mon Sep 17 00:00:00 2001 From: tysonlt Date: Tue, 13 Jul 2021 13:03:34 +1000 Subject: [PATCH 0089/1254] added support for 'relationships' manifest --- .../app/itemimport/ItemImportCLITool.java | 7 + .../app/itemimport/ItemImportServiceImpl.java | 136 +++++++++++++++++- .../itemimport/service/ItemImportService.java | 5 + 3 files changed, 147 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java index 7cad97df31..07c9eab8d9 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java @@ -94,6 +94,7 @@ public class ItemImportCLITool { options.addOption("R", "resume", false, "resume a failed import (add only)"); options.addOption("q", "quiet", false, "don't display metadata"); + options.addOption("l", "relationships", false, "process relationships manifest"); options.addOption("h", "help", false, "help"); @@ -107,6 +108,7 @@ public class ItemImportCLITool { String[] collections = null; // db ID or handles boolean isTest = false; boolean isResume = false; + boolean processRelationships = false; boolean useWorkflow = false; boolean useWorkflowSendEmail = false; boolean isQuiet = false; @@ -184,6 +186,10 @@ public class ItemImportCLITool { collections = line.getOptionValues('c'); } + if (line.hasOption('l')) { //relationships + processRelationships = true; + } + if (line.hasOption('R')) { isResume = true; System.out @@ -303,6 +309,7 @@ public class ItemImportCLITool { myloader.setResume(isResume); myloader.setUseWorkflow(useWorkflow); myloader.setUseWorkflowSendEmail(useWorkflowSendEmail); + myloader.setProcessRelationships(processRelationships); myloader.setQuiet(isQuiet); // create a context 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 c89b2d0723..98270b04fc 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 @@ -44,7 +44,6 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; - import org.apache.commons.collections4.ComparatorUtils; import org.apache.commons.io.FileDeleteStrategy; import org.apache.commons.io.FileUtils; @@ -68,6 +67,8 @@ import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; 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.BitstreamFormatService; import org.dspace.content.service.BitstreamService; @@ -77,6 +78,8 @@ import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; +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.core.Context; @@ -151,6 +154,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected WorkflowService workflowService; @Autowired(required = true) protected ConfigurationService configurationService; + @Autowired(required = true) + protected RelationshipService relationshipService; + @Autowired(required = true) + protected RelationshipTypeService relationshipTypeService; protected String tempWorkDir; @@ -159,6 +166,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected boolean useWorkflow = false; protected boolean useWorkflowSendEmail = false; protected boolean isQuiet = false; + protected boolean processRelationships = false; @Override public void afterPropertiesSet() throws Exception { @@ -211,10 +219,13 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea // create the mapfile File outFile = null; PrintWriter mapOut = null; + try { Map skipItems = new HashMap<>(); // set of items to skip if in 'resume' // mode + Map itemMap = new HashMap<>(); //remember which folder item was imported from + System.out.println("Adding items from directory: " + sourceDir); log.debug("Adding items from directory: " + sourceDir); System.out.println("Generating mapfile: " + mapFile); @@ -274,12 +285,23 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } else { clist = mycollections; } + Item item = addItem(c, clist, sourceDir, dircontents[i], mapOut, template); + + if (processRelationships) { + itemMap.put(dircontents[i], item); + } + c.uncacheEntity(item); System.out.println(i + " " + dircontents[i]); } } + if (processRelationships) { + //now that all items are imported, iterate again to link relationships + addRelationships(c, sourceDir, itemMap); + } + } finally { if (mapOut != null) { mapOut.flush(); @@ -288,6 +310,112 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } } + protected void addRelationships(Context c, String sourceDir, Map itemMap) throws Exception { + + System.out.println("Linking relationships"); + + for (Map.Entry itemEntry : itemMap.entrySet()) { + + String folderName = itemEntry.getKey(); + String path = sourceDir + File.separatorChar + folderName; + Item leftItem = itemEntry.getValue(); + + System.out.println("Adding relationships from directory "+ folderName); + + //look for a 'relationship' manifest + Map relationships = processRelationshipFile(path, "relationships"); + if (!relationships.isEmpty()) { + + for (Map.Entry relEntry : relationships.entrySet()) { + + Integer relationshipId = relEntry.getKey(); + String mappedItemFolder = relEntry.getValue(); + Item rightItem = itemMap.get(mappedItemFolder); + + RelationshipType relationshipType = null; + try { + relationshipType = relationshipTypeService.find(c, relationshipId.intValue()); + } catch (Exception e) { + System.out.println("\tERROR: relationship type "+ relationshipId +" not found."); + throw e; + } + + Relationship relationship = relationshipService.create(c, leftItem, rightItem, relationshipType, -1, -1); + + System.out.println("\tAdded relationship to " + rightItem.getHandle()); + + } + + } + + } + + } + + protected Map processRelationshipFile(String path, String filename) throws Exception { + + File file = new File(path + File.separatorChar + filename); + Map result = new HashMap(); + + if (file.exists()) { + + System.out.println("\tProcessing relationships file: " + filename); + + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(file)); + String line = null; + while ((line = br.readLine()) != null) { + line = line.trim(); + if ("".equals(line)) { + continue; + } + + int relationshipId; + String folderName = null; + + //format: + StringTokenizer st = new StringTokenizer(line); + + if (st.hasMoreTokens()) { + try { + relationshipId = Integer.valueOf(st.nextToken()); + } catch (NumberFormatException e) { + throw new Exception("Bad mapfile line:\n" + line); + } + } else { + throw new Exception("Bad mapfile line:\n" + line); + } + + if (st.hasMoreTokens()) { + folderName = st.nextToken(); + } else { + throw new Exception("Bad mapfile line:\n" + line); + } + + result.put(relationshipId, folderName); + + } + + } catch (FileNotFoundException e) { + System.out.println("\tNo relationships file found."); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + System.out.println("Non-critical problem releasing resources."); + } + } + } + + } else { + System.out.println("\tNo relationships file found."); + } + + return result; + } + @Override public void replaceItems(Context c, List mycollections, String sourceDir, String mapFile, boolean template) throws Exception { @@ -1823,4 +1951,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea public void setQuiet(boolean isQuiet) { this.isQuiet = isQuiet; } + + @Override + public void setProcessRelationships(boolean processRelationships) { + this.processRelationships = processRelationships; + } + } 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 af333764b5..ab4473ad08 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 @@ -229,6 +229,11 @@ public interface ItemImportService { */ public void setUseWorkflowSendEmail(boolean useWorkflowSendMail); + /** + * @param processRelationships whether to look for a relationships manifest + */ + public void setProcessRelationships(boolean processRelationships); + /** * Set quiet flag * From c35fba249a1610169dd1ae37a5ead6ea54c5d09e Mon Sep 17 00:00:00 2001 From: tysonlt Date: Tue, 13 Jul 2021 13:33:18 +1000 Subject: [PATCH 0090/1254] allow item lookup by uuid, handle, or import folder name --- .../app/itemimport/ItemImportServiceImpl.java | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) 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 98270b04fc..61be85f79e 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 @@ -310,6 +310,15 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } } + /** + * Add relationships from a 'relationships' manifest file. + * + * Each line in the file contains a relationship type id and an item identifier in the following format: + * + * + * + * The input_item_folder should refer the folder name of another item in this import batch. + */ protected void addRelationships(Context c, String sourceDir, Map itemMap) throws Exception { System.out.println("Linking relationships"); @@ -328,21 +337,24 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea for (Map.Entry relEntry : relationships.entrySet()) { - Integer relationshipId = relEntry.getKey(); - String mappedItemFolder = relEntry.getValue(); - Item rightItem = itemMap.get(mappedItemFolder); + Integer relationshipTypeId = relEntry.getKey(); + String itemIdentifier = relEntry.getValue(); RelationshipType relationshipType = null; try { - relationshipType = relationshipTypeService.find(c, relationshipId.intValue()); + relationshipType = relationshipTypeService.find(c, relationshipTypeId.intValue()); } catch (Exception e) { - System.out.println("\tERROR: relationship type "+ relationshipId +" not found."); + System.out.println("\tERROR: relationship type "+ relationshipTypeId +" not found."); throw e; } - Relationship relationship = relationshipService.create(c, leftItem, rightItem, relationshipType, -1, -1); + Item rightItem = resolveRelatedItem(c, itemMap, itemIdentifier); + if (null == rightItem) { + throw new Exception("\tERROR: could not find item for "+ itemIdentifier); + } - System.out.println("\tAdded relationship to " + rightItem.getHandle()); + Relationship relationship = relationshipService.create(c, leftItem, rightItem, relationshipType, -1, -1); + System.out.println("\tAdded relationship (type: "+ relationshipTypeId +") to "+ rightItem.getHandle()); } @@ -352,6 +364,27 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } + protected Item resolveRelatedItem(Context c, Map itemMap, String itemIdentifier) throws Exception { + + Item item = null; + + if (itemMap.containsKey(itemIdentifier)) { + //identifier refers to a folder name in this import batch + item = itemMap.get(itemIdentifier); + + } else if (itemIdentifier.indexOf('/') != -1) { + //resolve by handle + item = (Item) handleService.resolveToObject(c, itemIdentifier); + + } else { + //try to resolve by UUID + item = itemService.findByIdOrLegacyId(c, itemIdentifier); + } + + return item; + + } + protected Map processRelationshipFile(String path, String filename) throws Exception { File file = new File(path + File.separatorChar + filename); @@ -371,15 +404,15 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea continue; } - int relationshipId; - String folderName = null; + int relationshipTypeId; + String itemIdentifier = null; //format: StringTokenizer st = new StringTokenizer(line); if (st.hasMoreTokens()) { try { - relationshipId = Integer.valueOf(st.nextToken()); + relationshipTypeId = Integer.valueOf(st.nextToken()); } catch (NumberFormatException e) { throw new Exception("Bad mapfile line:\n" + line); } @@ -388,12 +421,12 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } if (st.hasMoreTokens()) { - folderName = st.nextToken(); + itemIdentifier = st.nextToken(); } else { throw new Exception("Bad mapfile line:\n" + line); } - result.put(relationshipId, folderName); + result.put(relationshipTypeId, itemIdentifier); } From 2455171a58f797a2a8bbb57335a0afc4b67143f2 Mon Sep 17 00:00:00 2001 From: tysonlt Date: Tue, 13 Jul 2021 14:06:47 +1000 Subject: [PATCH 0091/1254] support multiple relationships of the same type --- .../app/itemimport/ItemImportServiceImpl.java | 117 +++++++++++------- 1 file changed, 75 insertions(+), 42 deletions(-) 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 61be85f79e..daab4e13d9 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 @@ -310,15 +310,14 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } } - /** - * Add relationships from a 'relationships' manifest file. - * - * Each line in the file contains a relationship type id and an item identifier in the following format: - * - * - * - * The input_item_folder should refer the folder name of another item in this import batch. - */ + /** + * Add relationships from a 'relationships' manifest file. + * + * @param c Context + * @param sourceDir The parent import source directory + * @param itemMap Item imported in this batch, keyed by their import subfolder + * @throws Exception + */ protected void addRelationships(Context c, String sourceDir, Map itemMap) throws Exception { System.out.println("Linking relationships"); @@ -332,13 +331,13 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea System.out.println("Adding relationships from directory "+ folderName); //look for a 'relationship' manifest - Map relationships = processRelationshipFile(path, "relationships"); + Map> relationships = processRelationshipFile(path, "relationships"); if (!relationships.isEmpty()) { - for (Map.Entry relEntry : relationships.entrySet()) { + for (Map.Entry> relEntry : relationships.entrySet()) { Integer relationshipTypeId = relEntry.getKey(); - String itemIdentifier = relEntry.getValue(); + List identifierList = relEntry.getValue(); RelationshipType relationshipType = null; try { @@ -348,13 +347,17 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea throw e; } - Item rightItem = resolveRelatedItem(c, itemMap, itemIdentifier); - if (null == rightItem) { - throw new Exception("\tERROR: could not find item for "+ itemIdentifier); - } + for (String itemIdentifier : identifierList) { - Relationship relationship = relationshipService.create(c, leftItem, rightItem, relationshipType, -1, -1); - System.out.println("\tAdded relationship (type: "+ relationshipTypeId +") to "+ rightItem.getHandle()); + Item rightItem = resolveRelatedItem(c, itemMap, itemIdentifier); + if (null == rightItem) { + throw new Exception("\tERROR: could not find item for "+ itemIdentifier); + } + + Relationship relationship = relationshipService.create(c, leftItem, rightItem, relationshipType, -1, -1); + System.out.println("\tAdded relationship (type: "+ relationshipTypeId +") to "+ rightItem.getHandle()); + + } } @@ -364,31 +367,24 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } - protected Item resolveRelatedItem(Context c, Map itemMap, String itemIdentifier) throws Exception { - - Item item = null; - - if (itemMap.containsKey(itemIdentifier)) { - //identifier refers to a folder name in this import batch - item = itemMap.get(itemIdentifier); - - } else if (itemIdentifier.indexOf('/') != -1) { - //resolve by handle - item = (Item) handleService.resolveToObject(c, itemIdentifier); - - } else { - //try to resolve by UUID - item = itemService.findByIdOrLegacyId(c, itemIdentifier); - } - - return item; - - } - - protected Map processRelationshipFile(String path, String filename) throws Exception { + /** + * Read the relationship manifest file. + * + * Each line in the file contains a relationship type id and an item identifier in the following format: + * + * + * + * The input_item_folder should refer the folder name of another item in this import batch. + * + * @param path The main import folder path. + * @param filename The name of the manifest file to check ('relationships') + * @return Map of found relationships + * @throws Exception + */ + protected Map> processRelationshipFile(String path, String filename) throws Exception { File file = new File(path + File.separatorChar + filename); - Map result = new HashMap(); + Map> result = new HashMap<>(); if (file.exists()) { @@ -426,7 +422,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea throw new Exception("Bad mapfile line:\n" + line); } - result.put(relationshipTypeId, itemIdentifier); + if (!result.containsKey(relationshipTypeId)) { + result.put(relationshipTypeId, new ArrayList<>()); + } + + result.get(relationshipTypeId).add(itemIdentifier); } @@ -449,6 +449,39 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea return result; } + /** + * Resolve an item identifier referred to in the relationships manifest file. + * + * The import item map will be checked first to see if the identifier refers to an item folder + * that was just imported. Next it will try to find the item by handle or UUID. + * + * @param c Context + * @param itemMap Item imported in this batch, keyed by their import subfolder + * @param itemIdentifier The identifier string found in the import manifest (handle, uuid, or another import subfolder) + * @return Item if found, or null. + * @throws Exception + */ + protected Item resolveRelatedItem(Context c, Map itemMap, String itemIdentifier) throws Exception { + + Item item = null; + + if (itemMap.containsKey(itemIdentifier)) { + //identifier refers to a folder name in this import batch + item = itemMap.get(itemIdentifier); + + } else if (itemIdentifier.indexOf('/') != -1) { + //resolve by handle + item = (Item) handleService.resolveToObject(c, itemIdentifier); + + } else { + //try to resolve by UUID + item = itemService.findByIdOrLegacyId(c, itemIdentifier); + } + + return item; + + } + @Override public void replaceItems(Context c, List mycollections, String sourceDir, String mapFile, boolean template) throws Exception { From 4972ee3f9817bcfb37dd37ec17fbd6deb1a15391 Mon Sep 17 00:00:00 2001 From: tysonlt Date: Tue, 13 Jul 2021 14:54:48 +1000 Subject: [PATCH 0092/1254] populate item map with skipped items --- .../org/dspace/app/itemimport/ItemImportServiceImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 daab4e13d9..9c28890762 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 @@ -266,6 +266,14 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea for (int i = 0; i < dircontents.length; i++) { if (skipItems.containsKey(dircontents[i])) { System.out.println("Skipping import of " + dircontents[i]); + + if (processRelationships) { + //we still need the item in the map for relationship linking + String skippedHandle = skipItems.get(dircontents[i]); + Item skippedItem = (Item) handleService.resolveToObject(c, skippedHandle); + itemMap.put(dircontents[i], skippedItem); + } + } else { List clist; if (directoryFileCollections) { From e53ed6151a6f5fb3bd7f78fb6f90207734104277 Mon Sep 17 00:00:00 2001 From: tysonlt Date: Wed, 14 Jul 2021 13:01:03 +1000 Subject: [PATCH 0093/1254] lookup relationship by type, allow entity lookup by meta --- .../app/itemimport/ItemImportServiceImpl.java | 189 +++++++++++++----- .../dspace/app/util/RelationshipUtils.java | 50 +++++ 2 files changed, 194 insertions(+), 45 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java 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 9c28890762..10ffd6fb86 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 @@ -54,6 +54,7 @@ import org.apache.logging.log4j.Logger; import org.apache.xpath.XPathAPI; import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.app.util.LocalSchemaFilenameFilter; +import org.dspace.app.util.RelationshipUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; @@ -67,6 +68,7 @@ import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; import org.dspace.content.MetadataSchemaEnum; +import org.dspace.content.MetadataValue; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; @@ -78,6 +80,7 @@ import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; +import org.dspace.content.service.MetadataValueService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; @@ -158,6 +161,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected RelationshipService relationshipService; @Autowired(required = true) protected RelationshipTypeService relationshipTypeService; + @Autowired(required = true) + protected MetadataValueService metadataValueService; protected String tempWorkDir; @@ -168,6 +173,9 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected boolean isQuiet = false; protected boolean processRelationships = false; + //remember which folder item was imported from + Map itemFolderMap = null; + @Override public void afterPropertiesSet() throws Exception { tempWorkDir = configurationService.getProperty("org.dspace.app.batchitemimport.work.dir"); @@ -224,7 +232,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea Map skipItems = new HashMap<>(); // set of items to skip if in 'resume' // mode - Map itemMap = new HashMap<>(); //remember which folder item was imported from + itemFolderMap = new HashMap<>(); System.out.println("Adding items from directory: " + sourceDir); log.debug("Adding items from directory: " + sourceDir); @@ -271,7 +279,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea //we still need the item in the map for relationship linking String skippedHandle = skipItems.get(dircontents[i]); Item skippedItem = (Item) handleService.resolveToObject(c, skippedHandle); - itemMap.put(dircontents[i], skippedItem); + itemFolderMap.put(dircontents[i], skippedItem); } } else { @@ -297,7 +305,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea Item item = addItem(c, clist, sourceDir, dircontents[i], mapOut, template); if (processRelationships) { - itemMap.put(dircontents[i], item); + itemFolderMap.put(dircontents[i], item); } c.uncacheEntity(item); @@ -307,7 +315,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea if (processRelationships) { //now that all items are imported, iterate again to link relationships - addRelationships(c, sourceDir, itemMap); + addRelationships(c, sourceDir); } } finally { @@ -323,47 +331,75 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea * * @param c Context * @param sourceDir The parent import source directory - * @param itemMap Item imported in this batch, keyed by their import subfolder * @throws Exception */ - protected void addRelationships(Context c, String sourceDir, Map itemMap) throws Exception { + protected void addRelationships(Context c, String sourceDir) throws Exception { System.out.println("Linking relationships"); - for (Map.Entry itemEntry : itemMap.entrySet()) { + for (Map.Entry itemEntry : itemFolderMap.entrySet()) { String folderName = itemEntry.getKey(); String path = sourceDir + File.separatorChar + folderName; - Item leftItem = itemEntry.getValue(); + Item item = itemEntry.getValue(); System.out.println("Adding relationships from directory "+ folderName); //look for a 'relationship' manifest - Map> relationships = processRelationshipFile(path, "relationships"); + Map> relationships = processRelationshipFile(path, "relationships"); if (!relationships.isEmpty()) { - for (Map.Entry> relEntry : relationships.entrySet()) { + for (Map.Entry> relEntry : relationships.entrySet()) { - Integer relationshipTypeId = relEntry.getKey(); + String relationshipType = relEntry.getKey(); List identifierList = relEntry.getValue(); - RelationshipType relationshipType = null; - try { - relationshipType = relationshipTypeService.find(c, relationshipTypeId.intValue()); - } catch (Exception e) { - System.out.println("\tERROR: relationship type "+ relationshipTypeId +" not found."); - throw e; - } - for (String itemIdentifier : identifierList) { - Item rightItem = resolveRelatedItem(c, itemMap, itemIdentifier); - if (null == rightItem) { - throw new Exception("\tERROR: could not find item for "+ itemIdentifier); + //find referenced item + Item relationItem = resolveRelatedItem(c, itemIdentifier); + if (null == relationItem) { + throw new Exception("Could not find item for "+ itemIdentifier); } - Relationship relationship = relationshipService.create(c, leftItem, rightItem, relationshipType, -1, -1); - System.out.println("\tAdded relationship (type: "+ relationshipTypeId +") to "+ rightItem.getHandle()); + //get entity type of entity and item + String itemEntityType = getEntityType(item); + String relatedEntityType = getEntityType(relationItem); + + //find matching relationship type + List relTypes = relationshipTypeService.findByLeftwardOrRightwardTypeName(c, relationshipType); + RelationshipType foundRelationshipType = RelationshipUtils.matchRelationshipType(relTypes, relatedEntityType, itemEntityType, relationshipType); + if (foundRelationshipType == null) { + throw new Exception("No Relationship type found for:\n" + + "Target type: " + relatedEntityType + "\n" + + "Origin referer type: " + itemEntityType + "\n" + + "with typeName: " + relationshipType + ); + } + + boolean left = false; + if (foundRelationshipType.getLeftwardType().equalsIgnoreCase(relationshipType)) { + left = true; + } + + // Placeholder items for relation placing + Item leftItem = null; + Item rightItem = null; + if (left) { + leftItem = item; + rightItem = relationItem; + } else { + leftItem = relationItem; + rightItem = item; + } + + // Create the relationship + int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem); + int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem); + Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, foundRelationshipType, leftPlace, rightPlace); + relationshipService.update(c, persistedRelationship); + + System.out.println("\tAdded relationship (type: "+ foundRelationshipType.getID() +") from "+ leftItem.getHandle() +" to "+ rightItem.getHandle()); } @@ -375,12 +411,22 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } + /** + * Get the item's entity type from meta. + * + * @param item + * @return + */ + protected String getEntityType(Item item) throws Exception { + return itemService.getMetadata(item, "dspace", "entity", "type", Item.ANY).get(0).getValue(); + } + /** * Read the relationship manifest file. * * Each line in the file contains a relationship type id and an item identifier in the following format: * - * + * relation. * * The input_item_folder should refer the folder name of another item in this import batch. * @@ -389,10 +435,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea * @return Map of found relationships * @throws Exception */ - protected Map> processRelationshipFile(String path, String filename) throws Exception { + protected Map> processRelationshipFile(String path, String filename) throws Exception { File file = new File(path + File.separatorChar + filename); - Map> result = new HashMap<>(); + Map> result = new HashMap<>(); if (file.exists()) { @@ -408,33 +454,31 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea continue; } - int relationshipTypeId; + String relationshipType = null; String itemIdentifier = null; - //format: StringTokenizer st = new StringTokenizer(line); if (st.hasMoreTokens()) { - try { - relationshipTypeId = Integer.valueOf(st.nextToken()); - } catch (NumberFormatException e) { - throw new Exception("Bad mapfile line:\n" + line); + relationshipType= st.nextToken(); + if (relationshipType.split("\\.").length > 1) { + relationshipType = relationshipType.split("\\.")[1]; } } else { throw new Exception("Bad mapfile line:\n" + line); } if (st.hasMoreTokens()) { - itemIdentifier = st.nextToken(); + itemIdentifier = st.nextToken("").trim(); } else { throw new Exception("Bad mapfile line:\n" + line); } - if (!result.containsKey(relationshipTypeId)) { - result.put(relationshipTypeId, new ArrayList<>()); + if (!result.containsKey(relationshipType)) { + result.put(relationshipType, new ArrayList<>()); } - result.get(relationshipTypeId).add(itemIdentifier); + result.get(relationshipType).add(itemIdentifier); } @@ -464,26 +508,81 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea * that was just imported. Next it will try to find the item by handle or UUID. * * @param c Context - * @param itemMap Item imported in this batch, keyed by their import subfolder * @param itemIdentifier The identifier string found in the import manifest (handle, uuid, or another import subfolder) * @return Item if found, or null. * @throws Exception */ - protected Item resolveRelatedItem(Context c, Map itemMap, String itemIdentifier) throws Exception { + protected Item resolveRelatedItem(Context c, String itemIdentifier) throws Exception { - Item item = null; + if (itemIdentifier.contains(":")) { - if (itemMap.containsKey(itemIdentifier)) { - //identifier refers to a folder name in this import batch - item = itemMap.get(itemIdentifier); + if (itemIdentifier.startsWith("folderName:") || itemIdentifier.startsWith("rowName:")) { + //identifier refers to a folder name in this import + int i = itemIdentifier.indexOf(":"); + String folderName = itemIdentifier.substring(i + 1); + if (itemFolderMap.containsKey(folderName)) { + return itemFolderMap.get(folderName); + } + + } else { + + //lookup by meta value + int i = itemIdentifier.indexOf(":"); + String metaKey = itemIdentifier.substring(0, i); + String metaValue = itemIdentifier.substring(i + 1); + return findItemByMetaValue(c, metaKey, metaValue); + + } } else if (itemIdentifier.indexOf('/') != -1) { //resolve by handle - item = (Item) handleService.resolveToObject(c, itemIdentifier); + return (Item) handleService.resolveToObject(c, itemIdentifier); } else { //try to resolve by UUID - item = itemService.findByIdOrLegacyId(c, itemIdentifier); + return itemService.findByIdOrLegacyId(c, itemIdentifier); + } + + return null; + + } + + /** + * Lookup an item by a (unique) meta value. + * + * @param metaKey + * @param metaValue + * @return Item + * @throws Exception if single item not found. + */ + protected Item findItemByMetaValue(Context c, String metaKey, String metaValue) throws Exception { + + Item item = null; + + String mf[] = metaKey.split("\\."); + if (mf.length < 2) { + throw new Exception("Bad metadata field in reference: '" + metaKey + "' (expected syntax is schema.element[.qualifier])"); + } + String schema = mf[0]; + String element = mf[1]; + String qualifier = mf.length == 2 ? null : mf[2]; + try { + MetadataField mfo = metadataFieldService.findByElement(c, schema, element, qualifier); + Iterator mdv = metadataValueService.findByFieldAndValue(c, mfo, metaValue); + if (mdv.hasNext()) { + MetadataValue mdvVal = mdv.next(); + UUID uuid = mdvVal.getDSpaceObject().getID(); + if (mdv.hasNext()) { + throw new Exception("Ambiguous reference; multiple matches in db: " + metaKey); + } + item = itemService.find(c, uuid); + } + } catch (SQLException e) { + throw new Exception("Error looking up item by metadata reference: " + metaKey, e); + } + + if (item == null) { + throw new Exception("Item not found by metadata reference: " + metaKey); } return item; diff --git a/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java new file mode 100644 index 0000000000..a7f38d610e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java @@ -0,0 +1,50 @@ +package org.dspace.app.util; + +import java.util.List; + +import org.dspace.content.RelationshipType; + +public class RelationshipUtils { + + /** + * Matches two Entity types to a Relationship Type from a set of Relationship Types. + * + * @param relTypes set of Relationship Types. + * @param targetType entity type of target. + * @param originType entity type of origin referer. + * @return null or matched Relationship Type. + */ + public static RelationshipType matchRelationshipType(List relTypes, String targetType, String originType, String originTypeName) { + RelationshipType foundRelationshipType = null; + if (originTypeName.split("\\.").length > 1) { + originTypeName = originTypeName.split("\\.")[1]; + } + for (RelationshipType relationshipType : relTypes) { + // Is origin type leftward or righward + boolean isLeft = false; + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType)) { + isLeft = true; + } + if (isLeft) { + // Validate typeName reference + if (!relationshipType.getLeftwardType().equalsIgnoreCase(originTypeName)) { + continue; + } + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) && + relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) { + foundRelationshipType = relationshipType; + } + } else { + if (!relationshipType.getRightwardType().equalsIgnoreCase(originTypeName)) { + continue; + } + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) && + relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) { + foundRelationshipType = relationshipType; + } + } + } + return foundRelationshipType; + } + +} From a9f339724c6804724110fbac86782cb4aecfb4dc Mon Sep 17 00:00:00 2001 From: tysonlt Date: Wed, 14 Jul 2021 15:14:21 +1000 Subject: [PATCH 0094/1254] tweak comments and progress message --- .../org/dspace/app/itemimport/ItemImportServiceImpl.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 10ffd6fb86..9f0d142905 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 @@ -399,7 +399,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, foundRelationshipType, leftPlace, rightPlace); relationshipService.update(c, persistedRelationship); - System.out.println("\tAdded relationship (type: "+ foundRelationshipType.getID() +") from "+ leftItem.getHandle() +" to "+ rightItem.getHandle()); + System.out.println("\tAdded relationship (type: "+ relationshipType +") from "+ leftItem.getHandle() +" to "+ rightItem.getHandle()); } @@ -505,7 +505,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea * Resolve an item identifier referred to in the relationships manifest file. * * The import item map will be checked first to see if the identifier refers to an item folder - * that was just imported. Next it will try to find the item by handle or UUID. + * that was just imported. Next it will try to find the item by handle or UUID, or by a unique + * meta value. * * @param c Context * @param itemIdentifier The identifier string found in the import manifest (handle, uuid, or another import subfolder) From 16ffffc3a73472910efc7e059311e70ea4f4f067 Mon Sep 17 00:00:00 2001 From: tysonlt Date: Thu, 15 Jul 2021 11:32:13 +1000 Subject: [PATCH 0095/1254] fix checkstyle violations --- .../app/itemimport/ItemImportServiceImpl.java | 47 +++++----- .../dspace/app/util/RelationshipUtils.java | 87 +++++++++++-------- 2 files changed, 76 insertions(+), 58 deletions(-) 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 9f0d142905..f2916bc8a7 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 @@ -44,6 +44,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; + import org.apache.commons.collections4.ComparatorUtils; import org.apache.commons.io.FileDeleteStrategy; import org.apache.commons.io.FileUtils; @@ -232,7 +233,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea Map skipItems = new HashMap<>(); // set of items to skip if in 'resume' // mode - itemFolderMap = new HashMap<>(); + itemFolderMap = new HashMap<>(); System.out.println("Adding items from directory: " + sourceDir); log.debug("Adding items from directory: " + sourceDir); @@ -336,21 +337,21 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected void addRelationships(Context c, String sourceDir) throws Exception { System.out.println("Linking relationships"); - + for (Map.Entry itemEntry : itemFolderMap.entrySet()) { String folderName = itemEntry.getKey(); String path = sourceDir + File.separatorChar + folderName; Item item = itemEntry.getValue(); - System.out.println("Adding relationships from directory "+ folderName); + System.out.println("Adding relationships from directory " + folderName); //look for a 'relationship' manifest Map> relationships = processRelationshipFile(path, "relationships"); if (!relationships.isEmpty()) { for (Map.Entry> relEntry : relationships.entrySet()) { - + String relationshipType = relEntry.getKey(); List identifierList = relEntry.getValue(); @@ -359,16 +360,19 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea //find referenced item Item relationItem = resolveRelatedItem(c, itemIdentifier); if (null == relationItem) { - throw new Exception("Could not find item for "+ itemIdentifier); - } + throw new Exception("Could not find item for " + itemIdentifier); + } //get entity type of entity and item String itemEntityType = getEntityType(item); String relatedEntityType = getEntityType(relationItem); //find matching relationship type - List relTypes = relationshipTypeService.findByLeftwardOrRightwardTypeName(c, relationshipType); - RelationshipType foundRelationshipType = RelationshipUtils.matchRelationshipType(relTypes, relatedEntityType, itemEntityType, relationshipType); + List relTypes = relationshipTypeService.findByLeftwardOrRightwardTypeName( + c, relationshipType); + RelationshipType foundRelationshipType = RelationshipUtils.matchRelationshipType( + relTypes, relatedEntityType, itemEntityType, relationshipType); + if (foundRelationshipType == null) { throw new Exception("No Relationship type found for:\n" + "Target type: " + relatedEntityType + "\n" + @@ -396,10 +400,12 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea // Create the relationship int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem); int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem); - Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, foundRelationshipType, leftPlace, rightPlace); + Relationship persistedRelationship = relationshipService.create( + c, leftItem, rightItem, foundRelationshipType, leftPlace, rightPlace); relationshipService.update(c, persistedRelationship); - System.out.println("\tAdded relationship (type: "+ relationshipType +") from "+ leftItem.getHandle() +" to "+ rightItem.getHandle()); + System.out.println("\tAdded relationship (type: " + relationshipType + ") from " + + leftItem.getHandle() + " to " + rightItem.getHandle()); } @@ -460,14 +466,14 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea StringTokenizer st = new StringTokenizer(line); if (st.hasMoreTokens()) { - relationshipType= st.nextToken(); + relationshipType = st.nextToken(); if (relationshipType.split("\\.").length > 1) { relationshipType = relationshipType.split("\\.")[1]; } } else { throw new Exception("Bad mapfile line:\n" + line); } - + if (st.hasMoreTokens()) { itemIdentifier = st.nextToken("").trim(); } else { @@ -475,9 +481,9 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } if (!result.containsKey(relationshipType)) { - result.put(relationshipType, new ArrayList<>()); - } - + result.put(relationshipType, new ArrayList<>()); + } + result.get(relationshipType).add(itemIdentifier); } @@ -493,7 +499,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } } } - + } else { System.out.println("\tNo relationships file found."); } @@ -509,11 +515,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea * meta value. * * @param c Context - * @param itemIdentifier The identifier string found in the import manifest (handle, uuid, or another import subfolder) + * @param itemIdentifier The identifier string found in the import manifest (handle, uuid, or import subfolder) * @return Item if found, or null. * @throws Exception */ - protected Item resolveRelatedItem(Context c, String itemIdentifier) throws Exception { + protected Item resolveRelatedItem(Context c, String itemIdentifier) throws Exception { if (itemIdentifier.contains(":")) { @@ -524,7 +530,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea if (itemFolderMap.containsKey(folderName)) { return itemFolderMap.get(folderName); } - + } else { //lookup by meta value @@ -562,7 +568,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea String mf[] = metaKey.split("\\."); if (mf.length < 2) { - throw new Exception("Bad metadata field in reference: '" + metaKey + "' (expected syntax is schema.element[.qualifier])"); + throw new Exception("Bad metadata field in reference: '" + metaKey + + "' (expected syntax is schema.element[.qualifier])"); } String schema = mf[0]; String element = mf[1]; diff --git a/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java index a7f38d610e..8a8ac00c7c 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.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.util; import java.util.List; @@ -6,45 +13,49 @@ import org.dspace.content.RelationshipType; public class RelationshipUtils { + private RelationshipUtils() { + } + /** * Matches two Entity types to a Relationship Type from a set of Relationship Types. - * + * * @param relTypes set of Relationship Types. - * @param targetType entity type of target. - * @param originType entity type of origin referer. - * @return null or matched Relationship Type. - */ - public static RelationshipType matchRelationshipType(List relTypes, String targetType, String originType, String originTypeName) { - RelationshipType foundRelationshipType = null; - if (originTypeName.split("\\.").length > 1) { - originTypeName = originTypeName.split("\\.")[1]; - } - for (RelationshipType relationshipType : relTypes) { - // Is origin type leftward or righward - boolean isLeft = false; - if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType)) { - isLeft = true; - } - if (isLeft) { - // Validate typeName reference - if (!relationshipType.getLeftwardType().equalsIgnoreCase(originTypeName)) { - continue; - } - if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) && - relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) { - foundRelationshipType = relationshipType; - } - } else { - if (!relationshipType.getRightwardType().equalsIgnoreCase(originTypeName)) { - continue; - } - if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) && - relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) { - foundRelationshipType = relationshipType; - } - } - } - return foundRelationshipType; - } - + * @param targetType entity type of target. + * @param originType entity type of origin referer. + * @return null or matched Relationship Type. + */ + public static RelationshipType matchRelationshipType(List relTypes, String targetType, + String originType, String originTypeName) { + RelationshipType foundRelationshipType = null; + if (originTypeName.split("\\.").length > 1) { + originTypeName = originTypeName.split("\\.")[1]; + } + for (RelationshipType relationshipType : relTypes) { + // Is origin type leftward or righward + boolean isLeft = false; + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType)) { + isLeft = true; + } + if (isLeft) { + // Validate typeName reference + if (!relationshipType.getLeftwardType().equalsIgnoreCase(originTypeName)) { + continue; + } + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) + && relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) { + foundRelationshipType = relationshipType; + } + } else { + if (!relationshipType.getRightwardType().equalsIgnoreCase(originTypeName)) { + continue; + } + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) + && relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) { + foundRelationshipType = relationshipType; + } + } + } + return foundRelationshipType; + } + } From fc8f12f19892c23ddb5f892cf4f5aeb28b11b4dc Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Thu, 22 Jul 2021 14:04:33 -0400 Subject: [PATCH 0096/1254] DS-3334 Update SWORD and SWORDv2 URL configuration construction --- .../org/dspace/sword/SWORDUrlManager.java | 64 +++++++----------- .../org/dspace/sword2/SwordUrlManager.java | 65 +++++++------------ 2 files changed, 48 insertions(+), 81 deletions(-) diff --git a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java index 3b01e16f43..ba466f5247 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java +++ b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java @@ -7,8 +7,6 @@ */ package org.dspace.sword; -import java.net.MalformedURLException; -import java.net.URL; import java.sql.SQLException; import java.util.List; @@ -54,6 +52,12 @@ public class SWORDUrlManager { private final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private String swordPath = configurationService.getProperty( + "sword-server.path", "sword"); + + private String dspaceUrl = configurationService.getProperty( + "dspace.server.url"); + public SWORDUrlManager(SWORDConfiguration config, Context context) { this.config = config; this.context = context; @@ -307,24 +311,12 @@ public class SWORDUrlManager { String depositUrl = configurationService.getProperty( "sword-server.servicedocument.url"); if (depositUrl == null || "".equals(depositUrl)) { - String dspaceUrl = configurationService.getProperty( - "dspace.server.url"); if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSWORDException( "Unable to construct service document urls, due to missing/invalid " + "config in sword.servicedocument.url and/or dspace.server.url"); } - - try { - URL url = new URL(dspaceUrl); - depositUrl = new URL(url.getProtocol(), url.getHost(), - url.getPort(), "/sword/servicedocument").toString(); - } catch (MalformedURLException e) { - throw new DSpaceSWORDException( - "Unable to construct service document urls, due to invalid dspace.server.url " + - e.getMessage(), e); - } - + depositUrl = validSWORDUrl("/servicedocument"); } return depositUrl; } @@ -350,24 +342,12 @@ public class SWORDUrlManager { String depositUrl = configurationService.getProperty( "sword-server.deposit.url"); if (depositUrl == null || "".equals(depositUrl)) { - String dspaceUrl = configurationService.getProperty( - "dspace.server.url"); if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSWORDException( "Unable to construct deposit urls, due to missing/invalid config in " + "sword.deposit.url and/or dspace.server.url"); } - - try { - URL url = new URL(dspaceUrl); - depositUrl = new URL(url.getProtocol(), url.getHost(), - url.getPort(), "/sword/deposit").toString(); - } catch (MalformedURLException e) { - throw new DSpaceSWORDException( - "Unable to construct deposit urls, due to invalid dspace.server.url " + - e.getMessage(), e); - } - + depositUrl = validSWORDUrl("/deposit"); } return depositUrl; } @@ -454,24 +434,12 @@ public class SWORDUrlManager { String mlUrl = configurationService.getProperty( "sword-server", "media-link.url"); if (StringUtils.isBlank(mlUrl)) { - String dspaceUrl = configurationService.getProperty( - "dspace.server.url"); if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSWORDException( "Unable to construct media-link urls, due to missing/invalid config in " + "media-link.url and/or dspace.server.url"); } - - try { - URL url = new URL(dspaceUrl); - mlUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), - "/sword/media-link").toString(); - } catch (MalformedURLException e) { - throw new DSpaceSWORDException( - "Unable to construct media-link urls, due to invalid dspace.server.url " + - e.getMessage(), e); - } - + mlUrl = validSWORDUrl("/media-link"); } return mlUrl; } @@ -530,4 +498,18 @@ public class SWORDUrlManager { throw new DSpaceSWORDException(e); } } + + /** + * Ensure configured paths when combined are vail URLs + * + * @param path the target SWORD endpoint + * @return a valid sword URL + */ + private String validSWORDUrl(String path) { + String[] splitDspaceUrl = dspaceUrl.split("//"); + String url = splitDspaceUrl[1] + "/" + swordPath + "/" + path; + url = url.replace("///", "/"); + url = url.replace("//", "/"); + return splitDspaceUrl[0] + "//" + url; + } } diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java index 3fdb677c99..5b699fb575 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java @@ -7,8 +7,6 @@ */ package org.dspace.sword2; -import java.net.MalformedURLException; -import java.net.URL; import java.sql.SQLException; import java.util.List; @@ -48,6 +46,12 @@ public class SwordUrlManager { protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private String swordPath = configurationService.getProperty( + "swordv2-server.path", "swordv2"); + + private String dspaceUrl = configurationService.getProperty( + "dspace.server.url"); + /** * the SWORD configuration */ @@ -98,23 +102,12 @@ public class SwordUrlManager { throws DSpaceSwordException { String sUrl = configurationService.getProperty("swordv2-server.url"); if (sUrl == null || "".equals(sUrl)) { - String dspaceUrl = configurationService - .getProperty("dspace.server.url"); if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSwordException( "Unable to construct service document urls, due to missing/invalid " + "config in sword2.url and/or dspace.server.url"); } - - try { - URL url = new URL(dspaceUrl); - sUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), - "/swordv2").toString(); - } catch (MalformedURLException e) { - throw new DSpaceSwordException( - "Unable to construct service document urls, due to invalid dspace.server.url " + - e.getMessage(), e); - } + sUrl = validSWORDUrl("/swordv2"); } return sUrl; } @@ -283,7 +276,8 @@ public class SwordUrlManager { return dso; } else { throw new SwordError(DSpaceUriRegistry.BAD_URL, - "Service Document request does not refer to a DSpace Collection or Community"); + "Service Document request does not refer to a DSpace Collection " + + "or Community"); } } else { throw new SwordError(DSpaceUriRegistry.BAD_URL, @@ -306,23 +300,12 @@ public class SwordUrlManager { String sdUrl = configurationService .getProperty("swordv2-server.servicedocument.url"); if (sdUrl == null || "".equals(sdUrl)) { - String dspaceUrl = configurationService - .getProperty("dspace.server.url"); if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSwordException( "Unable to construct service document urls, due to missing/invalid " + "config in swordv2-server.cfg servicedocument.url and/or dspace.server.url"); } - - try { - URL url = new URL(dspaceUrl); - sdUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), - "/swordv2/servicedocument").toString(); - } catch (MalformedURLException e) { - throw new DSpaceSwordException( - "Unable to construct service document urls, due to invalid dspace.server.url " + - e.getMessage(), e); - } + sdUrl = validSWORDUrl("/servicedocument"); } return sdUrl; } @@ -348,24 +331,12 @@ public class SwordUrlManager { String depositUrl = configurationService .getProperty("swordv2-server.collection.url"); if (depositUrl == null || "".equals(depositUrl)) { - String dspaceUrl = configurationService - .getProperty("dspace.server.url"); if (dspaceUrl == null || "".equals(dspaceUrl)) { throw new DSpaceSwordException( "Unable to construct deposit urls, due to missing/invalid config in " + "swordv2-server.cfg deposit.url and/or dspace.server.url"); } - - try { - URL url = new URL(dspaceUrl); - depositUrl = new URL(url.getProtocol(), url.getHost(), - url.getPort(), "/swordv2/collection").toString(); - } catch (MalformedURLException e) { - throw new DSpaceSwordException( - "Unable to construct deposit urls, due to invalid dspace.server.url " + - e.getMessage(), e); - } - + depositUrl = validSWORDUrl("/collection"); } return depositUrl; } @@ -515,4 +486,18 @@ public class SwordUrlManager { return new IRI(this.getSwordBaseUrl() + "/edit-media/" + item.getID() + ".atom"); } + + /** + * Ensure configured paths when combined are vail URLs + * + * @param path the target SWORD endpoint + * @return a valid sword URL + */ + private String validSWORDUrl(String path) { + String[] splitDspaceUrl = dspaceUrl.split("//"); + String url = splitDspaceUrl[1] + "/" + swordPath + "/" + path; + url = url.replace("///", "/"); + url = url.replace("//", "/"); + return splitDspaceUrl[0] + "//" + url; + } } From 3d9df3923168d4ec88b3524db2503f2b09e028f1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 29 Jul 2021 14:09:30 -0500 Subject: [PATCH 0097/1254] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 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 +++++++++++++++--------------- 14 files changed, 29 insertions(+), 29 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 03685d53bd..fdad2d29dc 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 7a6336ee46..b4609c6ea7 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 172e7cc796..16528cd1ad 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index bc61fefe7d..aaeb6ec690 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.0 + 7.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.0 + 7.1-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 0008090847..67926a5562 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 17ca993bef..d7c0a4d36e 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.0 + 7.1-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index a5f09f56ab..38576fb363 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 6a0a2027b7..8543e10752 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 2ebce27c6f..b7a7c65048 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index a7ca9d0123..925a7c1e32 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.0 + 7.1-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index b75f25a9de..b1129f90f4 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 6e1bef443c..01cdf2c940 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.0 + 7.1-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index affeb26f9e..e10f455b26 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.0 + 7.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 13509777e9..6be4d348bc 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.0 + 7.1-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -827,14 +827,14 @@ org.dspace dspace-rest - 7.0 + 7.1-SNAPSHOT jar classes org.dspace dspace-rest - 7.0 + 7.1-SNAPSHOT war @@ -981,64 +981,64 @@ org.dspace dspace-api - 7.0 + 7.1-SNAPSHOT org.dspace dspace-api test-jar - 7.0 + 7.1-SNAPSHOT test org.dspace.modules additions - 7.0 + 7.1-SNAPSHOT org.dspace dspace-sword - 7.0 + 7.1-SNAPSHOT org.dspace dspace-swordv2 - 7.0 + 7.1-SNAPSHOT org.dspace dspace-oai - 7.0 + 7.1-SNAPSHOT org.dspace dspace-services - 7.0 + 7.1-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.0 + 7.1-SNAPSHOT test org.dspace dspace-rdf - 7.0 + 7.1-SNAPSHOT org.dspace dspace-server-webapp - 7.0 + 7.1-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.0 + 7.1-SNAPSHOT war @@ -1820,7 +1820,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.0 + HEAD From 8acbab766dc05adc8207bab6c5754b12fa6addab Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 1 Aug 2021 12:23:38 -0700 Subject: [PATCH 0098/1254] Added 3rd party licenses. --- LICENSES_THIRD_PARTY | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 73688416ae..cd0a82fb5d 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -145,6 +145,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * ehcache (net.sf.ehcache:ehcache:2.10.6 - http://ehcache.org) * Ehcache Core (net.sf.ehcache:ehcache-core:2.6.11 - http://ehcache.org) * Ehcache 3 (org.ehcache:ehcache:3.4.0 - https://www.ehcache.org/) + * cache-config (javax.cache:cache-api:1.1.0 - https://mvnrepository.com/artifact/javax.cache/cache-api) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) * Apache Ant Core (org.apache.ant:ant:1.10.9 - https://ant.apache.org/) @@ -344,6 +345,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * Spring Boot Log4j 2 Starter (org.springframework.boot:spring-boot-starter-log4j2:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-log4j2) * Spring Boot Security Starter (org.springframework.boot:spring-boot-starter-security:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-security) * Spring Boot Test Starter (org.springframework.boot:spring-boot-starter-test:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-test) + * Spring Boot Cache Starter (org.springframework.boot:spring-boot-starter-cache::2.2.6.RELEASE - https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache) * Spring Boot Tomcat Starter (org.springframework.boot:spring-boot-starter-tomcat:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-tomcat) * Spring Boot Validation Starter (org.springframework.boot:spring-boot-starter-validation:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-validation) * Spring Boot Web Starter (org.springframework.boot:spring-boot-starter-web:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-web) From 0f9686f756f5a378938d7435b24c8f261cfaf6e3 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 1 Aug 2021 12:23:59 -0700 Subject: [PATCH 0099/1254] Added missing license header. --- .../java/org/dspace/app/rest/iiif/cache/CacheConfig.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java index a6bde31bba..56a1851eef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.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.iiif.cache; import org.springframework.cache.annotation.EnableCaching; From 03df9052906c5a6bf6ff266651a861a915d106a1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 1 Aug 2021 17:10:41 -0700 Subject: [PATCH 0100/1254] Removed unused import. --- .../src/main/java/org/dspace/app/rest/Application.java | 1 - 1 file changed, 1 deletion(-) 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 34c90ee338..993c28af92 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 @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; import org.springframework.hateoas.server.LinkRelationProvider; From 7e7bee02d29725f9d0cc7eb55703ff320dd84548 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 16:57:14 +0000 Subject: [PATCH 0101/1254] Bump ant from 1.10.9 to 1.10.11 Bumps ant from 1.10.9 to 1.10.11. --- updated-dependencies: - dependency-name: org.apache.ant:ant 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 6be4d348bc..11a0cad2c7 100644 --- a/pom.xml +++ b/pom.xml @@ -1266,7 +1266,7 @@ org.apache.ant ant - 1.10.9 + 1.10.11 org.apache.jena From f41b91d9de6b837c63cdc30e39b49cd2e491ab81 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 2 Aug 2021 16:33:59 -0400 Subject: [PATCH 0102/1254] Finish documenting DOIService. --- .../dspace/identifier/service/DOIService.java | 67 +++++++++++++++++-- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java index 9af1fd8a0a..2cbefc8895 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java @@ -17,26 +17,65 @@ import org.dspace.identifier.IdentifierException; import org.dspace.identifier.doi.DOIIdentifierException; /** - * Service interface class for the DOI object. - * The implementation of this class is responsible for all business logic calls for the DOI object and is autowired - * by spring + * Service interface class for the {@link DOI} object. + * The implementation of this class is responsible for all business logic calls + * for the {@link DOI} object and is autowired by Spring. * * @author kevinvandevelde at atmire.com */ public interface DOIService { + /** + * Update a DOI in storage. + * + * @param context current DSpace session. + * @param doi the DOI to persist. + * @throws SQLException passed through. + */ public void update(Context context, DOI doi) throws SQLException; + /** + * Create a new DOI in storage. + * + * @param context current DSpace session. + * @return the new DOI. + * @throws SQLException passed through. + */ public DOI create(Context context) throws SQLException; + /** + * Find a specific DOI in storage. + * + * @param context current DSpace session. + * @param doi string representation of the DOI. + * @return the DOI object found. + * @throws SQLException passed through, can mean none found. + */ public DOI findByDoi(Context context, String doi) throws SQLException; + /** + * Find the DOI assigned to a given DSpace Object. + * + * @param context current DSpace session. + * @param dso The DSpace Object. + * @return the DSO's DOI. + * @throws SQLException passed through. + */ public DOI findDOIByDSpaceObject(Context context, DSpaceObject dso) throws SQLException; + /** + * Find the DOI assigned to a given DSpace Object, unless it has one of a + * given set of statuses. + * + * @param context current DSpace context. + * @param dso the DSpace Object. + * @param statusToExclude uninteresting statuses. + * @return the DSO's DOI. + * @throws SQLException passed through. + */ public DOI findDOIByDSpaceObject(Context context, DSpaceObject dso, List statusToExclude) throws SQLException; - /** * This method helps to convert a DOI into a URL. It takes DOIs in one of * the following formats and returns it as URL (f.e. @@ -55,6 +94,12 @@ public interface DOIService { public String DOIToExternalForm(String identifier) throws IdentifierException; + /** + * Convert an HTTP DOI URL (https://doi.org/10.something) to a "doi:" URI. + * @param identifier HTTP URL + * @return DOI URI + * @throws DOIIdentifierException if {@link identifier} is not recognizable. + */ public String DOIFromExternalFormat(String identifier) throws DOIIdentifierException; @@ -64,16 +109,24 @@ public interface DOIService { * @param identifier Identifier to format, following format are accepted: * f.e. 10.123/456, doi:10.123/456, http://dx.doi.org/10.123/456. * @return Given Identifier with DOI-Scheme, f.e. doi:10.123/456. - * @throws IllegalArgumentException If identifier is empty or null. - * @throws org.dspace.identifier.doi.DOIIdentifierException If DOI could not be recognized. + * @throws IllegalArgumentException If identifier is empty or null. + * @throws DOIIdentifierException If DOI could not be recognized. */ public String formatIdentifier(String identifier) throws DOIIdentifierException; + /** + * Find all DOIs that have one of a given set of statuses. + * @param context current DSpace session. + * @param statuses desired statuses. + * @return all DOIs having any of the given statuses. + * @throws SQLException passed through. + */ public List getDOIsByStatus(Context context, List statuses) throws SQLException; /** - * Find all DOIs that are similar to the specified pattern ant not in the specified states. + * Find all DOIs that are similar to the specified pattern and not in the + * specified states. * * @param context DSpace context * @param doiPattern The pattern, e.g. "10.5072/123.%" From e6898ddf774f7cd46933e5c6d217dc50c3e4b34c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 2 Aug 2021 16:48:54 -0500 Subject: [PATCH 0103/1254] Minor code cleanup. Remove any references to BTE, XMLUI and JSPUI from 7.x codebase --- .../app/itemimport/ItemImportCLITool.java | 45 +---- .../itemimport/service/ItemImportService.java | 4 +- .../authority/DSpaceControlledVocabulary.java | 2 +- ...mmonsTextStreamDisseminationCrosswalk.java | 2 +- .../content/packager/RoleDisseminator.java | 2 +- .../org/dspace/importer/external/README.md | 159 ------------------ .../license/CreativeCommonsServiceImpl.java | 2 +- .../service/CreativeCommonsService.java | 2 +- .../src/main/java/org/dspace/rdf/RDFUtil.java | 4 +- .../SimpleDSORelationsConverterPlugin.java | 9 +- dspace-oai/src/test/resources/item.xml | 4 +- dspace-oai/src/test/resources/xoai-test1.xml | 16 +- dspace/config/dspace.cfg | 10 +- dspace/config/local.cfg.EXAMPLE | 5 +- dspace/config/modules/authentication-ldap.cfg | 1 - dspace/config/modules/oai.cfg | 2 - .../modules/rdf/constant-data-general.ttl | 2 +- .../modules/rdf/metadata-rdf-mapping.ttl | 6 +- dspace/config/modules/workflow.cfg | 2 +- dspace/config/spring/api/discovery.xml | 6 - 20 files changed, 34 insertions(+), 251 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/importer/external/README.md diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java index 7cad97df31..b34b1132d0 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java @@ -76,7 +76,6 @@ public class ItemImportCLITool { options.addOption("r", "replace", false, "replace items in mapfile"); options.addOption("d", "delete", false, "delete items listed in mapfile"); - options.addOption("i", "inputtype", true, "input type in case of BTE import"); options.addOption("s", "source", true, "source of items (directory)"); options.addOption("z", "zip", true, "name of zip file"); options.addOption("c", "collection", true, @@ -100,7 +99,6 @@ public class ItemImportCLITool { CommandLine line = parser.parse(options, argv); String command = null; // add replace remove, etc - String bteInputType = null; //ris, endnote, tsv, csv, bibtex String sourcedir = null; String mapfile = null; String eperson = null; // db ID or email @@ -144,14 +142,6 @@ public class ItemImportCLITool { command = "delete"; } - if (line.hasOption('b')) { - command = "add-bte"; - } - - if (line.hasOption('i')) { - bteInputType = line.getOptionValue('i'); - } - if (line.hasOption('w')) { useWorkflow = true; if (line.hasOption('n')) { @@ -235,37 +225,6 @@ public class ItemImportCLITool { System.out.println("No collections given. Assuming 'collections' file inside item directory"); commandLineCollections = false; } - } else if ("add-bte".equals(command)) { - //Source dir can be null, the user can specify the parameters for his loader in the Spring XML - // configuration file - - if (mapfile == null) { - System.out - .println("Error - a map file to hold importing results must be specified"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); - } - - if (eperson == null) { - System.out - .println("Error - an eperson to do the importing must be specified"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); - } - - if (collections == null) { - System.out.println("No collections given. Assuming 'collections' file inside item directory"); - commandLineCollections = false; - } - - if (bteInputType == null) { - System.out - .println( - "Error - an input type (tsv, csv, ris, endnote, bibtex or any other type you have " + - "specified in BTE Spring XML configuration file) must be specified"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); - } } else if ("delete".equals(command)) { if (eperson == null) { System.out @@ -280,9 +239,9 @@ public class ItemImportCLITool { } // can only resume for adds - if (isResume && !"add".equals(command) && !"add-bte".equals(command)) { + if (isResume && !"add".equals(command)) { System.out - .println("Error - resume option only works with the --add or the --add-bte commands"); + .println("Error - resume option only works with the --add command"); System.exit(1); } 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 af333764b5..2d648e2416 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 @@ -105,7 +105,7 @@ public interface ItemImportService { String inputType, Context context, boolean template) throws Exception; /** - * Since the BTE batch import is done in a new thread we are unable to communicate + * If a batch import is done in a new thread we are unable to communicate * with calling method about success or failure. We accomplish this * communication with email instead. Send a success email once the batch * import is complete @@ -119,7 +119,7 @@ public interface ItemImportService { String fileName) throws MessagingException; /** - * Since the BTE batch import is done in a new thread we are unable to communicate + * If a batch import is done in a new thread we are unable to communicate * with calling method about success or failure. We accomplis this * communication with email instead. Send an error email if the batch * import fails 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 adce37865d..dfaf4a107f 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 @@ -30,7 +30,7 @@ import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** - * ChoiceAuthority source that reads the JSPUI-style hierarchical vocabularies + * ChoiceAuthority source that reads the hierarchical vocabularies * from {@code ${dspace.dir}/config/controlled-vocabularies/*.xml} and turns * them into autocompleting authorities. * diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java index b0e1a74a3e..edb9d60a62 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/CreativeCommonsTextStreamDisseminationCrosswalk.java @@ -29,7 +29,7 @@ import org.dspace.license.service.CreativeCommonsService; * Export the object's Creative Commons license, text form. * * @author Larry Stone - * @deprecated to make uniform JSPUI and XMLUI approach the bitstream with the + * @deprecated the bitstream with the * license in the textual format it is no * longer stored (see https://jira.duraspace.org/browse/DS-2604) */ diff --git a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java index 93a2f446d0..8643f60f6c 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java @@ -534,7 +534,7 @@ public class RoleDisseminator implements PackageDisseminator { } // FINAL CATCH-ALL -> Find any other groups where name begins with "COLLECTION__" - // (Necessary cause XMLUI allows you to generate a 'COLLECTION__DEFAULT_READ' group) + // (Necessary because the old XMLUI allowed you to generate a 'COLLECTION__DEFAULT_READ' group) List matchingGroups = groupService.search(context, "COLLECTION\\_" + collection.getID() + "\\_"); for (Group g : matchingGroups) { if (!list.contains(g)) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/README.md b/dspace-api/src/main/java/org/dspace/importer/external/README.md deleted file mode 100644 index 66f22d9e27..0000000000 --- a/dspace-api/src/main/java/org/dspace/importer/external/README.md +++ /dev/null @@ -1,159 +0,0 @@ -- [Introduction](#Introduction) - - [Features](#Features) - - [Abstraction of input format](#Abstraction-input-format) - - [Transformation to DSpace item](#transformation) - - [Relation with BTE](#bte) -- [Implementation of an import source](#Example-implementation) - - [Inherited methods](#Inherited-methods) - - [Metadata mapping](#Mapping) - - -# Introduction # - -This documentation explains the features and the usage of the importer framework. -Enabling the framework can be achieved by removing the comment block from the following step in item-submission.xml -Implementation specific or additional configuration can be found in their related documentation, if any. (Some implementations use other submission steps altogether, so make sure to double check) - -``` - - submit.progressbar.lookup - org.dspace.submit.step.XMLUIStartSubmissionLookupStep - org.dspace.app.webui.submit.step.JSPStartSubmissionLookupStep - org.dspace.app.xmlui.aspect.submission.submit.StartSubmissionLookupStep - true - -``` - -## Features ## - -- lookup publications from remote sources -- Support for multiple implementations - -## Abstraction of input format ## - -The importer framework does not enforce a specific input format. Each importer implementation defines which input format it expects from a remote source. -The import framework uses generics to achieve this. Each importer implementation will have a type set of the record type it receives from the remote source's response. -This type set will also be used by the framework to use the correct MetadataFieldMapping for a certain implementation. Read [Implementation of an import source](#Example-implementation) for more information. - -## Transformation to DSpace item ## - -The framework produces an 'ImportRecord' that is completely decoupled from DSPace. It contains a set of metadata DTO's that contain the notion of schema,element and qualifier. The specific implementation is responsible for populating this set. It is then very simple to create a DSPace item from this list. - -## Relation with BTE ## - -While there is some overlap between this framework and BTE, this framework supports some features that are hard to implement using the BTE. It has explicit support to deal with network failure and throttling imposed by the data source. It also has explicit support for distinguishing between network caused errors and invalid requests to the source. -Furthermore the framework doesn't impose any restrictions on the format in which the data is retrieved. It uses java generics to support different source record types. A reference implementation of using XML records is provided for which a set of metadata can be generated from any xpath expression (or composite of xpath expressions). -Unless 'advanced' processing is necessary (e.g. lookup of authors in an LDAP directory) this metadata mapping can be simply configured using spring. No code changes necessary. A mixture of advanced and simple (xpath) mapping is also possible. - -This design is also in line with the roadmap to create a Modular Framework as detailed in [https://wiki.duraspace.org/display/DSPACE/Design+-+Module+Framework+and+Registry](https://wiki.duraspace.org/display/DSPACE/Design+-+Module+Framework+and+Registry) -This modular design also allows it to be completely independent of the user interface layer, be it JSPUI, XMLUI, command line or the result of the new UI projects: [https://wiki.duraspace.org/display/DSPACE/Design+-+Single+UI+Project](https://wiki.duraspace.org/display/DSPACE/Design+-+Single+UI+Project) - -# Implementation of an import source # - -Each importer implementation must at least implement interface *org.dspace.importer.external.service.components.MetadataSource* and implement the inherited methods. - -One can also choose to implement class *org.dspace.importer.external.service.components.AbstractRemoteMetadataSource* next to the MetadataSource interface. This class contains functionality to handle request timeouts and to retry requests. - -A third option is to implement class *org.dspace.importer.external.service.AbstractImportSourceService*. This class already implements both the MetadataSource interface and Source class. AbstractImportSourceService has a generic type set 'RecordType'. In the importer implementation this type set should be the class of the records received from the remote source's response (e.g. when using axiom to get the records from the remote source's XML response, the importer implementation's type set is *org.apache.axiom.om.OMElement*). - -Implementing the AbstractImportSourceService allows the importer implementation to use the framework's build-in support to transform a record received from the remote source to an object of class *org.dspace.importer.external.datamodel.ImportRecord* containing DSpace metadata fields, as explained here: [Metadata mapping](#Mapping). - -## Inherited methods ## - -Method getImportSource() should return a unique identifier. Importer implementations should not be called directly, but class *org.dspace.importer.external.service.ImportService* should be called instead. This class contains the same methods as the importer implementations, but with an extra parameter 'url'. This url parameter should contain the same identifier that is returned by the getImportSource() method of the importer implementation you want to use. - -The other inherited methods are used to query the remote source. - -## Metadata mapping ## - -When using an implementation of AbstractImportSourceService, a mapping of remote record fields to DSpace metadata fields can be created. - -first create an implementation of class AbstractMetadataFieldMapping with the same type set used for the importer implementation. - -Then create a spring configuration file in [dspace.dir]/config/spring/api. - -Each DSpace metadata field that will be used for the mapping must first be configured as a spring bean of class *org.dspace.importer.external.metadatamapping.MetadataFieldConfig*. - -```xml - - - -``` - -Now this metadata field can be used to create a mapping. To add a mapping for the "dc.title" field declared above, a new spring bean configuration of a class class *org.dspace.importer.external.metadatamapping.contributor.MetadataContributor* needs to be added. This interface contains a type argument. -The type needs to match the type used in the implementation of AbstractImportSourceService. The responsibility of each MetadataContributor implementation is to generate a set of metadata from the retrieved document. How it does that is completely opaque to the AbstractImportSourceService but it is assumed that only one entity (i.e. item) is fed to the metadatum contributor. - - -For example ```java SimpleXpathMetadatumContributor implements MetadataContributor``` can parse a fragment of xml and generate one or more metadata values. - - -This bean expects 2 property values: - -- field: A reference to the configured spring bean of the DSpace metadata field. e.g. the "dc.title" bean declared above. -- query: The xpath expression used to select the record value returned by the remote source. - -```xml - - - - -``` - -Multiple record fields can also be combined into one value. To implement a combined mapping first create a *SimpleXpathMetadatumContributor* as explained above for each part of the field. - -```xml - - - - - - - - -``` - -Note that namespace prefixes used in the xpath queries are configured in bean "FullprefixMapping" in the same spring file. - -```xml - - Defines the namespace mappin for the SimpleXpathMetadatum contributors - - - -``` - -Then create a new list in the spring configuration containing references to all *SimpleXpathMetadatumContributor* beans that need to be combined. - -```xml - - - - {{/code}} -``` - -Finally create a spring bean configuration of class *org.dspace.importer.external.metadatamapping.contributor.CombinedMetadatumContributor*. This bean expects 3 values: - -- field: A reference to the configured spring bean of the DSpace metadata field. e.g. the "dc.title" bean declared above. -- metadatumContributors: A reference to the list containing all the single record field mappings that need to be combined. -- separator: These characters will be added between each record field value when they are combined into one field. - -```xml - - - - - -``` - -Each contributor must also be added to the "MetadataFieldMap" used by the *MetadataFieldMapping* implementation. Each entry of this map maps a metadata field bean to a contributor. For the contributors created above this results in the following configuration: - -```xml - - - - -``` - -Note that the single field mappings used for the combined author mapping are not added to this list. - 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 40e727d9df..31105cf495 100644 --- a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java @@ -67,7 +67,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi protected static final String BSN_LICENSE_URL = "license_url"; /** - * @deprecated to make uniform JSPUI and XMLUI approach the bitstream with the license in the textual format it + * @deprecated the bitstream with the license in the textual format it * is no longer stored (see https://jira.duraspace.org/browse/DS-2604) */ @Deprecated diff --git a/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java b/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java index 727979476a..1f5f1ddd02 100644 --- a/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java +++ b/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java @@ -124,7 +124,7 @@ public interface CreativeCommonsService { * @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. - * @deprecated to make uniform JSPUI and XMLUI approach the bitstream with the license in the textual format it + * @deprecated the bitstream with the license in the textual format it * is no longer stored (see https://jira.duraspace.org/browse/DS-2604) */ @Deprecated diff --git a/dspace-api/src/main/java/org/dspace/rdf/RDFUtil.java b/dspace-api/src/main/java/org/dspace/rdf/RDFUtil.java index 03ae589c62..1e9744aec5 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/RDFUtil.java +++ b/dspace-api/src/main/java/org/dspace/rdf/RDFUtil.java @@ -48,7 +48,7 @@ public class RDFUtil { public static final String STORAGE_GRAPHSTORE_PASSWORD_KEY = "rdf.storage.graphstore.password"; /** * Property key to load the URL of the dspace-rdf module. This is necessary - * to create links from the jspui or xmlui to RDF representation of + * to create links from the UI to RDF representation of * DSpaceObjects. */ public static final String CONTEXT_PATH_KEY = "rdf.contextPath"; @@ -289,7 +289,7 @@ public class RDFUtil { public static void isPublic(Context context, DSpaceObject dso) throws SQLException, ItemNotArchivedException, ItemWithdrawnException, ItemNotDiscoverableException, AuthorizeException { - // as there is no way to set site permissions in XMLUI or JSPUI, we + // as there is no way to set site permissions in UI, we // ignore the permissions of the repository root (DSpaceObject of type // Site). if (dso instanceof Site) { diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/SimpleDSORelationsConverterPlugin.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/SimpleDSORelationsConverterPlugin.java index 409a4f1518..63382a7c26 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/SimpleDSORelationsConverterPlugin.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/SimpleDSORelationsConverterPlugin.java @@ -453,14 +453,13 @@ public class SimpleDSORelationsConverterPlugin } /** - * This methods generataes a link to the provieded Bitstream. + * This methods generates a link to the provided Bitstream. * As bitstreams currently don't get Persistent Identifier in DSpace, we have - * to link them using a link to the repository. This link should work with - * JSPUI and XMLUI (at least it does in DSpace 4.x). + * to link them using a link to the repository. * * @param context The relevant DSpace Context. * @param bitstream Bitstream for which a URL should be generated. - * @return The link to the URL or null if the Bistream is is a Community or + * @return The link to the URL or null if the Bitstream is a Community or * Collection logo. * @throws SQLException if database error */ @@ -476,7 +475,7 @@ public class SimpleDSORelationsConverterPlugin String dspaceURL = configurationService.getProperty("dspace.ui.url"); String link = ""; try { - // this currently (DSpace 4.1) works with xmlui and jspui. + // the link to the bitstream in the UI link = dspaceURL + "/bitstream/" + parent.getHandle() + "/" + bitstream.getSequenceID() + "/" + Util.encodeBitstreamName(bitstream.getName(), Constants.DEFAULT_ENCODING); diff --git a/dspace-oai/src/test/resources/item.xml b/dspace-oai/src/test/resources/item.xml index c6eebc4cf7..b020a490bf 100644 --- a/dspace-oai/src/test/resources/item.xml +++ b/dspace-oai/src/test/resources/item.xml @@ -70,7 +70,7 @@ 10.1.1.13.814.pdf application/pdf 449025 - http://localhost:8080/xmlui/bitstream/123456789/3/1/10.1.1.13.814.pdf + http://localhost:8080/bitstream/123456789/3/1/10.1.1.13.814.pdf de2290628b85b17abdf829d121793c77 MD5 1 @@ -85,7 +85,7 @@ license.txt text/plain; charset=utf-8 1748 - http://localhost:8080/xmlui/bitstream/123456789/3/2/license.txt + http://localhost:8080/bitstream/123456789/3/2/license.txt 8a4605be74aa9ea9d79846c1fba20a33 MD5 2 diff --git a/dspace-oai/src/test/resources/xoai-test1.xml b/dspace-oai/src/test/resources/xoai-test1.xml index a621d0518f..0053e63e10 100644 --- a/dspace-oai/src/test/resources/xoai-test1.xml +++ b/dspace-oai/src/test/resources/xoai-test1.xml @@ -115,7 +115,7 @@ text/html 39970 - http://demo.dspace.org/xmlui/bitstream/10673/4/1/Lily-cat-of-day.htm + http://demo.dspace.org/bitstream/10673/4/1/Lily-cat-of-day.htm aae901336b56ae14070fdec8c79dd48e MD5 @@ -125,7 +125,7 @@ cl_style.css text/css 1181 - http://demo.dspace.org/xmlui/bitstream/10673/4/2/cl_style.css + http://demo.dspace.org/bitstream/10673/4/2/cl_style.css 58178d41c221520d88333b9d482b31b8 MD5 @@ -136,7 +136,7 @@ image/jpeg 83390 - http://demo.dspace.org/xmlui/bitstream/10673/4/3/kitty-1257950690.jpg + http://demo.dspace.org/bitstream/10673/4/3/kitty-1257950690.jpg 729596bb3ad09ebe958a150787418212 MD5 @@ -147,7 +147,7 @@ image/jpeg 86057 - http://demo.dspace.org/xmlui/bitstream/10673/4/4/kitty-1257950690_002.jpg + http://demo.dspace.org/bitstream/10673/4/4/kitty-1257950690_002.jpg 37c07c8488042d064bd945765d9e2f98 MD5 @@ -157,7 +157,7 @@ logo.png image/png 4157 - http://demo.dspace.org/xmlui/bitstream/10673/4/5/logo.png + http://demo.dspace.org/bitstream/10673/4/5/logo.png d64fb5f69c7820685d5ed3037b9670ed MD5 5 @@ -166,7 +166,7 @@ style.css text/css 49623 - http://demo.dspace.org/xmlui/bitstream/10673/4/6/style.css + http://demo.dspace.org/bitstream/10673/4/6/style.css 89c2a0557993a8665574c0b52fc1582d MD5 6 @@ -175,7 +175,7 @@ stylesheet.css text/css 877 - http://demo.dspace.org/xmlui/bitstream/10673/4/7/stylesheet.css + http://demo.dspace.org/bitstream/10673/4/7/stylesheet.css d2b580ac1c89ae88dc05dcd9108b002e MD5 @@ -191,7 +191,7 @@ license.txt text/plain; charset=utf-8 1748 - http://demo.dspace.org/xmlui/bitstream/10673/4/8/license.txt + http://demo.dspace.org/bitstream/10673/4/8/license.txt 8a4605be74aa9ea9d79846c1fba20a33 MD5 diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 5a307ebfc6..122cf9c044 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -868,13 +868,9 @@ metadata.hide.dc.description.provenance = true cc.api.rooturl = http://api.creativecommons.org/rest/1.5 # Metadata field to hold CC license URI of selected license -# NB: DSpace (both JSPUI and XMLUI) presentation code expects 'dc.rights.uri' to hold CC data. If you change -# this to another field, please consult documentation on how to update UI configuration cc.license.uri = dc.rights.uri # Metadata field to hold CC license name of selected license (if defined) -# NB: DSpace (both JSPUI and XMLUI) presentation code expects 'dc.rights' to hold CC data. If you change -# this to another field, please consult documentation on how to update UI configuration cc.license.name = dc.rights # Assign license name during web submission @@ -1023,9 +1019,9 @@ webui.preview.brand.fontpoint = 12 # DESCending (useful for dates - ie. most recent submissions) # NOTE: the text to render the index will use the parameter to select -# the message key from Messages.properties (for JSPUI) using a key of the form: +# the message key from the UI language packs key of the form: # -# browse.type.item. +# browse.metadata. # # Note: the index numbers must start from 1 and increment continuously by 1 # thereafter. Deviation from this will cause an error during install or @@ -1113,7 +1109,6 @@ plugin.named.org.dspace.sort.OrderFormatDelegate= \ # that metadata element in common (i.e. all the papers by a single author). If # it is a "full" type, it will link to a view of the standard full browse page, # starting with the value of the link clicked on. -# (This setting is not used by the XMLUI, as links are controlled by your theme) # # The default below defines the authors to link to other publications by that author # @@ -1155,7 +1150,6 @@ plugin.named.org.dspace.content.license.LicenseArgumentFormatter = \ # TODO: UNSUPPORTED in DSpace 7.0. Will be added in a later release # enable syndication feeds - links display on community and collection home pages -# (This setting is not used by XMLUI, as you enable feeds in your theme) webui.feed.enable = true # number of DSpace items per feed (the most recent submissions) webui.feed.items = 4 diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index f74541bca9..e724da0268 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -152,9 +152,8 @@ db.schema = public # handle.canonical.prefix = http://hdl.handle.net/ # handle.canonical.prefix = https://hdl.handle.net/ # -# Note that this will not alter dc.identifer.uri metadata for existing -# items (only for subsequent submissions), but it will alter the URL -# in JSPUI's 'identifier' message on item record pages for existing items. +# Note that this will not alter dc.identifier.uri metadata for existing +# items (only for subsequent submissions). # CNRI Handle prefix # (Defaults to a dummy/fake prefix of 123456789) diff --git a/dspace/config/modules/authentication-ldap.cfg b/dspace/config/modules/authentication-ldap.cfg index fa445c5714..bcc29ccac5 100644 --- a/dspace/config/modules/authentication-ldap.cfg +++ b/dspace/config/modules/authentication-ldap.cfg @@ -27,7 +27,6 @@ # With the setting off, users will be required to register and login with # their email address. With this setting on, users will be able to login # and register with their LDAP user ids and passwords. -# This setting is only used by the JSPUI. authentication-ldap.enable = false diff --git a/dspace/config/modules/oai.cfg b/dspace/config/modules/oai.cfg index 4509d7511f..79488afc29 100644 --- a/dspace/config/modules/oai.cfg +++ b/dspace/config/modules/oai.cfg @@ -67,8 +67,6 @@ oai.import.batch.size = 1000 # Crosswalk settings; the {name} value must correspond to a declared ingestion crosswalk # oai.harvester.metadataformats.{name} = {namespace},{optional display name} -# The display name is only used in the xmlui; for the jspui there are entries in the -# Messages.properties in the form jsp.tools.edit-collection.form.label21.select.{name} oai.harvester.metadataformats.dc = http://www.openarchives.org/OAI/2.0/oai_dc/\, Simple Dublin Core oai.harvester.metadataformats.qdc = http://purl.org/dc/terms/\, Qualified Dublin Core oai.harvester.metadataformats.dim = http://www.dspace.org/xmlns/dspace/dim\, DSpace Intermediate Metadata diff --git a/dspace/config/modules/rdf/constant-data-general.ttl b/dspace/config/modules/rdf/constant-data-general.ttl index 4930b94f5e..0ff3a430fa 100644 --- a/dspace/config/modules/rdf/constant-data-general.ttl +++ b/dspace/config/modules/rdf/constant-data-general.ttl @@ -1,7 +1,7 @@ @prefix void: . @prefix foaf: . -<> foaf:homepage ; +<> foaf:homepage ; void:sparqlEndpoint ; . diff --git a/dspace/config/modules/rdf/metadata-rdf-mapping.ttl b/dspace/config/modules/rdf/metadata-rdf-mapping.ttl index 07b978a448..341d8b4760 100644 --- a/dspace/config/modules/rdf/metadata-rdf-mapping.ttl +++ b/dspace/config/modules/rdf/metadata-rdf-mapping.ttl @@ -196,14 +196,14 @@ :localHandleURI dm:metadataName "dc.identifier.uri" ; - dm:condition "^http://localhost:8080/jspui/handle/" ; + dm:condition "^http://localhost:8080/handle/" ; dm:creates [ dm:subject dm:DSpaceObjectIRI ; dm:predicate bibo:handle ; dm:object [ a dm:LiteralGenerator ; dm:modifier [ - dm:matcher "^http://localhost:8080/jspui/handle/(.*)$" ; + dm:matcher "^http://localhost:8080/handle/(.*)$" ; dm:replacement "hdl:$1"; ]; dm:pattern "$DSpaceValue"; @@ -249,7 +249,7 @@ :uri dm:metadataName "dc.identifier.uri" ; - dm:condition "^((?!http://dx.doi.org/)(?!http://hdl.handle.net/)(?!http://localhost:8080/jspui/handle/)).*" ; + dm:condition "^((?!http://dx.doi.org/)(?!http://hdl.handle.net/)(?!http://localhost:8080/handle/)).*" ; dm:creates [ dm:subject dm:DSpaceObjectIRI ; dm:predicate bibo:uri ; diff --git a/dspace/config/modules/workflow.cfg b/dspace/config/modules/workflow.cfg index 4cca3a3222..8d11df03d5 100644 --- a/dspace/config/modules/workflow.cfg +++ b/dspace/config/modules/workflow.cfg @@ -2,7 +2,7 @@ #--------CONFIGURABLE REVIEWER WORKFLOW CONFIGURATIONS----------# #---------------------------------------------------------------# # Configuration properties used solely by the Configurable # -# Reviewer Workflow (XMLUI only) # +# Reviewer Workflow # #---------------------------------------------------------------# # # Workflow framework used by DSpace is now determined by the configured diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 1e56a26e77..4392e02cb3 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -235,8 +235,6 @@ - dc.title @@ -376,8 +374,6 @@ - dc.title @@ -518,8 +514,6 @@ - dc.title From 377542697c45813f8101b6fe6c676b70537d8279 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 2 Aug 2021 16:56:33 -0500 Subject: [PATCH 0104/1254] Cleanup a few more instances of JSPUI/XMLUI --- .../java/org/dspace/license/CreativeCommonsServiceImpl.java | 2 +- dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java | 5 +---- dspace-rest/src/main/webapp/static/reports/restReport.js | 5 +---- 3 files changed, 3 insertions(+), 9 deletions(-) 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 31105cf495..ccc660b63b 100644 --- a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java @@ -219,7 +219,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi return getLicenseURI(item); } - // JSPUI backward compatibility see https://jira.duraspace.org/browse/DS-2604 + // backward compatibility see https://jira.duraspace.org/browse/DS-2604 return getStringFromBitstream(context, item, BSN_LICENSE_URL); } diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java index 24bc00cce4..b98db57356 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonTest.java @@ -994,10 +994,7 @@ public class EPersonTest extends AbstractUnitTest { wfGroup.addMember(groupMember); groupService.update(context, wfGroup); - // DSpace currently contains two workflow systems. The newer XMLWorfklow needs additional tables that are not - // part of the test database yet. While it is expected that it becomes the default workflow system (DS-2059) - // one day, this won't happen before it its backported to JSPUI (DS-2121). - // TODO: add tests using the configurable workflowsystem + // Start workflow int wfiID = workflowService.startWithoutNotify(context, wsi).getID(); context.restoreAuthSystemState(); context.commit(); diff --git a/dspace-rest/src/main/webapp/static/reports/restReport.js b/dspace-rest/src/main/webapp/static/reports/restReport.js index 8abe17be06..747e78f75a 100644 --- a/dspace-rest/src/main/webapp/static/reports/restReport.js +++ b/dspace-rest/src/main/webapp/static/reports/restReport.js @@ -11,10 +11,7 @@ var Report = function() { this.COUNT_LIMIT = 500; this.ITEM_LIMIT = 100; - //set default to work on demo.dspace.org - this.ROOTPATH = "/xmlui/handle/" - //this.ROOTPATH = "/jspui/handle/" - //this.ROOTPATH = "/handle/" + this.ROOTPATH = "/handle/" //Indicate if Password Authentication is supported this.makeAuthLink = function(){return false;}; From 83371b44833d8a168c21b4c5c00aa63a4811f9d4 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 2 Aug 2021 18:16:30 -0400 Subject: [PATCH 0105/1254] Make the canonical DOI URL schema+authority configurable. #3287 --- .../main/java/org/dspace/identifier/DOI.java | 2 - .../identifier/DOIIdentifierProvider.java | 2 +- .../org/dspace/identifier/DOIServiceImpl.java | 79 +++++++++++++++---- .../dspace/identifier/service/DOIService.java | 7 ++ .../identifier/DOIIdentifierProviderTest.java | 6 +- 5 files changed, 76 insertions(+), 20 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOI.java b/dspace-api/src/main/java/org/dspace/identifier/DOI.java index b73fb2b155..8bb0ea2674 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOI.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOI.java @@ -34,8 +34,6 @@ public class DOI implements Identifier, ReloadableEntity { public static final String SCHEME = "doi:"; - public static final String RESOLVER = "http://dx.doi.org"; - @Id @Column(name = "doi_id") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "doi_seq") 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 c8e9636bcb..1476c9b7a2 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -769,7 +769,7 @@ public class DOIIdentifierProvider Item item = (Item) dso; List metadata = itemService.getMetadata(item, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null); - String leftPart = DOI.RESOLVER + SLASH + getPrefix() + SLASH + getNamespaceSeparator(); + String leftPart = doiService.getResolver() + SLASH + getPrefix() + SLASH + getNamespaceSeparator(); for (MetadataValue id : metadata) { if (id.getValue().startsWith(leftPart)) { return doiService.DOIFromExternalFormat(id.getValue()); diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java index aca933aab6..a4c3501d32 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java @@ -17,11 +17,13 @@ import org.dspace.core.Context; import org.dspace.identifier.dao.DOIDAO; import org.dspace.identifier.doi.DOIIdentifierException; import org.dspace.identifier.service.DOIService; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; /** - * Service implementation for the DOI object. - * This class is responsible for all business logic calls for the DOI object and is autowired by spring. + * Service implementation for the {@link DOI} object. + * This class is responsible for all business logic calls for the DOI object + * and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -31,6 +33,14 @@ public class DOIServiceImpl implements DOIService { @Autowired(required = true) protected DOIDAO doiDAO; + @Autowired(required = true) + protected ConfigurationService configurationService; + + private static final Pattern DOI_URL_PATTERN + = Pattern.compile("http(s)?://([a-z0-9-.]+)?doi.org(?/.*)", + Pattern.CASE_INSENSITIVE); + private static final String DOI_URL_PATTERN_PATH_GROUP = "path"; + protected DOIServiceImpl() { } @@ -66,25 +76,46 @@ public class DOIServiceImpl implements DOIService { if (null == identifier) { throw new IllegalArgumentException("Identifier is null.", new NullPointerException()); } + if (identifier.isEmpty()) { throw new IllegalArgumentException("Cannot format an empty identifier."); } - if (identifier.startsWith(DOI.SCHEME)) { - return DOI.RESOLVER + "/" + identifier.substring(DOI.SCHEME.length()); + + String resolver = getResolver(); + + if (identifier.startsWith(DOI.SCHEME)) { // doi:something + StringBuilder result = new StringBuilder(resolver); + if (!resolver.endsWith("/")) { + result.append('/'); + } + result.append(identifier.substring(DOI.SCHEME.length())); + return result.toString(); } - if (identifier.startsWith("10.") && identifier.contains("/")) { - return DOI.RESOLVER + "/" + identifier; + + if (identifier.startsWith("10.") && identifier.contains("/")) { // 10.something + StringBuilder result = new StringBuilder(resolver); + if (!resolver.endsWith("/")) { + result.append('/'); + } + result.append(identifier); + return result.toString(); } - if (identifier.startsWith(DOI.RESOLVER + "/10.")) { + + if (identifier.startsWith(resolver + "/10.")) { // https://doi.org/10.something return identifier; } + Matcher matcher = DOI_URL_PATTERN.matcher(identifier); + if (DOI_URL_PATTERN.matcher(identifier).matches()) { // various old URL forms + return resolver + matcher.group(DOI_URL_PATTERN_PATH_GROUP); + } + throw new IdentifierException(identifier + "does not seem to be a DOI."); } @Override public String DOIFromExternalFormat(String identifier) throws DOIIdentifierException { - Pattern pattern = Pattern.compile("^" + DOI.RESOLVER + "/+(10\\..*)$"); + Pattern pattern = Pattern.compile("^" + getResolver() + "/+(10\\..*)$"); Matcher matcher = pattern.matcher(identifier); if (matcher.find()) { return DOI.SCHEME + matcher.group(1); @@ -99,18 +130,29 @@ public class DOIServiceImpl implements DOIService { if (null == identifier) { throw new IllegalArgumentException("Identifier is null.", new NullPointerException()); } - if (identifier.startsWith(DOI.SCHEME)) { - return identifier; - } + if (identifier.isEmpty()) { throw new IllegalArgumentException("Cannot format an empty identifier."); } - if (identifier.startsWith("10.") && identifier.contains("/")) { + + if (identifier.startsWith(DOI.SCHEME)) { // doi:something + return identifier; + } + + if (identifier.startsWith("10.") && identifier.contains("/")) { // 10.something return DOI.SCHEME + identifier; } - if (identifier.startsWith(DOI.RESOLVER + "/10.")) { - return DOI.SCHEME + identifier.substring(18); + + String resolver = getResolver(); + if (identifier.startsWith(resolver + "/10.")) { //https://doi.org/10.something + return DOI.SCHEME + identifier.substring(resolver.length()); } + + Matcher matcher = DOI_URL_PATTERN.matcher(identifier); + if (matcher.matches()) { // various old URL forms + return DOI.SCHEME + matcher.group(DOI_URL_PATTERN_PATH_GROUP).substring(1); + } + throw new DOIIdentifierException(identifier + "does not seem to be a DOI.", DOIIdentifierException.UNRECOGNIZED); } @@ -126,4 +168,13 @@ public class DOIServiceImpl implements DOIService { throws SQLException { return doiDAO.findSimilarNotInState(context, doiPattern, statuses, dsoIsNotNull); } + + @Override + public String getResolver() { + String resolver = configurationService.getProperty("identifier.doi.resolver", "https://doi.org"); + if (resolver.endsWith("/")) { + resolver = resolver.substring(0, resolver.length() - 1); + } + return resolver; + } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java index 2cbefc8895..c9817b8eab 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java @@ -138,4 +138,11 @@ public interface DOIService { public List getSimilarDOIsNotInState(Context context, String doiPattern, List statuses, boolean dsoIsNotNull) throws SQLException; + + /** + * Get the URL stem of the DOI resolver, e.g. "https://doi.org/". + * + * @return URL to the DOI resolver. + */ + public String getResolver(); } 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 d79ba60450..7f75ae19a0 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -187,7 +187,7 @@ public class DOIIdentifierProviderTest List remainder = new ArrayList<>(); for (MetadataValue id : metadata) { - if (!id.getValue().startsWith(DOI.RESOLVER)) { + if (!id.getValue().startsWith(doiService.getResolver())) { remainder.add(id.getValue()); } } @@ -274,11 +274,11 @@ public class DOIIdentifierProviderTest PREFIX + "/" + NAMESPACE_SEPARATOR + "lkjljasd1234", DOI.SCHEME + "10.5072/123abc-lkj/kljl", "http://dx.doi.org/10.5072/123abc-lkj/kljl", - DOI.RESOLVER + "/10.5072/123abc-lkj/kljl" + doiService.getResolver() + "/10.5072/123abc-lkj/kljl" }; for (String doi : validDOIs) { - assertTrue("DOI should be supported", provider.supports(doi)); + assertTrue("DOI " + doi + " should be supported", provider.supports(doi)); } } From aa77a4277bda4de132c03257dd71239122140058 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 3 Aug 2021 10:21:18 -0400 Subject: [PATCH 0106/1254] Add commented default value to DSpace configuration. #3278 Tidy javadoc, make configuration default easier to find. --- .../src/main/java/org/dspace/identifier/DOIServiceImpl.java | 5 ++++- .../main/java/org/dspace/identifier/service/DOIService.java | 4 ++-- dspace/config/dspace.cfg | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java index a4c3501d32..4372d14a6a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java @@ -40,6 +40,8 @@ public class DOIServiceImpl implements DOIService { = Pattern.compile("http(s)?://([a-z0-9-.]+)?doi.org(?/.*)", Pattern.CASE_INSENSITIVE); private static final String DOI_URL_PATTERN_PATH_GROUP = "path"; + + private static final String RESOLVER_DEFAULT = "https://doi.org"; protected DOIServiceImpl() { @@ -171,7 +173,8 @@ public class DOIServiceImpl implements DOIService { @Override public String getResolver() { - String resolver = configurationService.getProperty("identifier.doi.resolver", "https://doi.org"); + String resolver = configurationService.getProperty("identifier.doi.resolver", + RESOLVER_DEFAULT); if (resolver.endsWith("/")) { resolver = resolver.substring(0, resolver.length() - 1); } diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java index c9817b8eab..5bd68a9061 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java @@ -88,8 +88,8 @@ public interface DOIService { * * @param identifier A DOI that should be returned in external form. * @return A String containing a URL to the official DOI resolver. - * @throws IllegalArgumentException If identifier is null or an empty String. - * @throws org.dspace.identifier.IdentifierException If identifier could not be recognized as valid DOI. + * @throws IllegalArgumentException If identifier is null or an empty String. + * @throws IdentifierException If identifier could not be recognized as valid DOI. */ public String DOIToExternalForm(String identifier) throws IdentifierException; diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 5a307ebfc6..34f6a46f5e 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -219,9 +219,14 @@ logging.server.max-payload-length = 10000 # Credentials used to authenticate against the registration agency: identifier.doi.user = username identifier.doi.password = password + +# URL for the DOI resolver. This will be the stem for generated DOIs. +#identifier.doi.resolver = https://doi.org + # DOI prefix used to mint DOIs. All DOIs minted by DSpace will use this prefix. # The Prefix will be assigned by the registration agency. identifier.doi.prefix = 10.5072 + # If you want to, you can further separate your namespace. Should all the # suffixes of all DOIs minted by DSpace start with a special string to separate # it from other services also minting DOIs under your prefix? From a8de4b39762c63415673f8fae20b5302e12ff727 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 3 Aug 2021 10:45:20 -0400 Subject: [PATCH 0107/1254] Satisfy checkstyle. #3287 --- .../src/main/java/org/dspace/identifier/DOIServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java index 4372d14a6a..780c33acf3 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java @@ -40,7 +40,7 @@ public class DOIServiceImpl implements DOIService { = Pattern.compile("http(s)?://([a-z0-9-.]+)?doi.org(?/.*)", Pattern.CASE_INSENSITIVE); private static final String DOI_URL_PATTERN_PATH_GROUP = "path"; - + private static final String RESOLVER_DEFAULT = "https://doi.org"; protected DOIServiceImpl() { @@ -173,7 +173,7 @@ public class DOIServiceImpl implements DOIService { @Override public String getResolver() { - String resolver = configurationService.getProperty("identifier.doi.resolver", + String resolver = configurationService.getProperty("identifier.doi.resolver", RESOLVER_DEFAULT); if (resolver.endsWith("/")) { resolver = resolver.substring(0, resolver.length() - 1); From b43e5dfdd88cda2fbfd8d416b20fec2035be28a6 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 4 Aug 2021 11:41:18 -0400 Subject: [PATCH 0108/1254] [DS-4548] Rename DSpace's LogManager to LogHelper. --- .../org/dspace/administer/RegistryLoader.java | 6 ++-- .../dspace/app/bulkedit/MetadataImport.java | 10 +++--- .../app/itemexport/ItemExportServiceImpl.java | 4 +-- .../app/itemimport/ItemImportServiceImpl.java | 4 +-- .../sherpa/submit/SHERPASubmitService.java | 4 +-- .../dspace/app/sitemap/GenerateSitemaps.java | 6 ++-- .../dspace/app/statistics/LogAnalyser.java | 12 +++---- .../dspace/authenticate/IPAuthentication.java | 12 +++---- .../authenticate/LDAPAuthentication.java | 36 +++++++++---------- .../authenticate/PasswordAuthentication.java | 12 +++---- .../authenticate/X509Authentication.java | 24 ++++++------- .../authority/AuthorityValueServiceImpl.java | 4 +-- .../java/org/dspace/browse/BrowseEngine.java | 22 ++++++------ .../content/BitstreamFormatServiceImpl.java | 12 +++---- .../dspace/content/BitstreamServiceImpl.java | 14 ++++---- .../org/dspace/content/BundleServiceImpl.java | 22 ++++++------ .../dspace/content/CollectionServiceImpl.java | 20 +++++------ .../dspace/content/CommunityServiceImpl.java | 20 +++++------ .../org/dspace/content/ItemServiceImpl.java | 32 ++++++++--------- .../content/MetadataFieldServiceImpl.java | 8 ++--- .../content/MetadataSchemaServiceImpl.java | 8 ++--- .../content/MetadataValueServiceImpl.java | 6 ++-- .../content/RelationshipServiceImpl.java | 4 +-- .../content/WorkspaceItemServiceImpl.java | 14 ++++---- .../packager/AbstractMETSDisseminator.java | 4 +-- .../packager/AbstractMETSIngester.java | 14 ++++---- .../packager/AbstractPackageIngester.java | 8 ++--- .../dspace/content/packager/PDFPackager.java | 4 +-- .../main/java/org/dspace/core/Context.java | 4 +-- .../core/{LogManager.java => LogHelper.java} | 4 +-- .../curate/XmlWorkflowCuratorServiceImpl.java | 4 +-- .../org/dspace/discovery/SolrServiceImpl.java | 11 +++--- ...erviceIndexCollectionSubmittersPlugin.java | 4 +-- .../SolrServicePrivateItemPlugin.java | 4 +-- .../SolrServiceResourceRestrictionPlugin.java | 6 ++-- .../indexobject/ItemIndexFactoryImpl.java | 6 ++-- .../org/dspace/eperson/EPersonConsumer.java | 6 ++-- .../dspace/eperson/EPersonServiceImpl.java | 8 ++--- .../org/dspace/eperson/GroupServiceImpl.java | 8 ++--- .../org/dspace/eperson/SubscribeCLITool.java | 8 ++--- .../dspace/eperson/SubscribeServiceImpl.java | 4 +-- .../service/impl/ExternalDataServiceImpl.java | 4 +-- .../identifier/HandleIdentifierProvider.java | 16 ++++----- .../VersionedHandleIdentifierProvider.java | 12 +++---- ...dentifierProviderWithCanonicalHandles.java | 16 ++++----- .../dspace/scripts/ProcessServiceImpl.java | 12 +++---- .../statistics/AnonymizeStatistics.java | 2 +- .../export/IrusExportUsageEventListener.java | 4 +-- .../usage/LoggerUsageEventListener.java | 4 +-- .../xmlworkflow/XmlWorkflowServiceImpl.java | 32 ++++++++--------- .../migration/RestartWorkflow.java | 4 +-- .../AssignOriginalSubmitterAction.java | 4 +-- .../userassignment/AutoAssignAction.java | 10 +++--- .../actions/userassignment/ClaimAction.java | 8 ++--- .../XmlWorkflowItemServiceImpl.java | 8 ++--- .../dspace/app/rest/OpenSearchController.java | 5 ++- .../EPersonRestAuthenticationProvider.java | 11 +++--- .../app/rest/utils/DiscoverQueryBuilder.java | 4 +-- .../org/dspace/sword/DSpaceSWORDServer.java | 15 ++++---- .../java/org/dspace/sword/DepositManager.java | 4 +-- .../org/dspace/sword/SWORDAuthenticator.java | 8 ++--- .../CollectionDepositManagerDSpace.java | 7 ++-- .../dspace/sword2/ContainerManagerDSpace.java | 22 ++++++------ .../org/dspace/sword2/DSpaceSwordAPI.java | 4 +-- .../sword2/MediaResourceManagerDSpace.java | 16 ++++----- .../sword2/ServiceDocumentManagerDSpace.java | 6 ++-- .../dspace/sword2/StatementManagerDSpace.java | 6 ++-- .../org/dspace/sword2/SwordAuthenticator.java | 8 ++--- 68 files changed, 330 insertions(+), 345 deletions(-) rename dspace-api/src/main/java/org/dspace/core/{LogManager.java => LogHelper.java} (98%) diff --git a/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java b/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java index b2f7280252..2b6a01b558 100644 --- a/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java +++ b/dspace-api/src/main/java/org/dspace/administer/RegistryLoader.java @@ -24,7 +24,7 @@ import org.dspace.content.BitstreamFormat; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamFormatService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -95,7 +95,7 @@ public class RegistryLoader { System.exit(1); } catch (Exception e) { - log.fatal(LogManager.getHeader(context, "error_loading_registries", + log.fatal(LogHelper.getHeader(context, "error_loading_registries", ""), e); System.err.println("Error: \n - " + e.getMessage()); @@ -135,7 +135,7 @@ public class RegistryLoader { loadFormat(context, n); } - log.info(LogManager.getHeader(context, "load_bitstream_formats", + log.info(LogHelper.getHeader(context, "load_bitstream_formats", "number_loaded=" + typeNodes.getLength())); } 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 0db0cc45be..7cd56ef620 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 @@ -53,7 +53,7 @@ import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.handle.factory.HandleServiceFactory; @@ -640,7 +640,7 @@ public class MetadataImport extends DSpaceRunnable getISSNs(Context context, Item item) { Set issns = new LinkedHashSet(); if (configuration.getIssnItemExtractors() == null) { - log.warn(LogManager.getHeader(context, "searchRelatedJournals", + log.warn(LogHelper.getHeader(context, "searchRelatedJournals", "no issnItemExtractors defined")); return null; } diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java index 5057477e31..d65447d311 100644 --- a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java +++ b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java @@ -40,7 +40,7 @@ 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.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.SearchService; @@ -284,7 +284,7 @@ public class GenerateSitemaps { if (makeHTMLMap) { int files = html.finish(); - log.info(LogManager.getHeader(c, "write_sitemap", + log.info(LogHelper.getHeader(c, "write_sitemap", "type=html,num_files=" + files + ",communities=" + comms.size() + ",collections=" + colls.size() + ",items=" + itemCount)); @@ -292,7 +292,7 @@ public class GenerateSitemaps { if (makeSitemapOrg) { int files = sitemapsOrg.finish(); - log.info(LogManager.getHeader(c, "write_sitemap", + log.info(LogHelper.getHeader(c, "write_sitemap", "type=html,num_files=" + files + ",communities=" + comms.size() + ",collections=" + colls.size() + ",items=" + itemCount)); diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java b/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java index 0ece47d32d..264fb1b317 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java @@ -31,7 +31,7 @@ import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.Utils; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.SearchServiceException; @@ -1144,17 +1144,17 @@ public class LogAnalyser { if (match.matches()) { // set up a new log line object LogLine logLine = new LogLine(parseDate(match.group(1).trim()), - LogManager.unescapeLogField(match.group(2)).trim(), - LogManager.unescapeLogField(match.group(3)).trim(), - LogManager.unescapeLogField(match.group(4)).trim(), - LogManager.unescapeLogField(match.group(5)).trim()); + LogHelper.unescapeLogField(match.group(2)).trim(), + LogHelper.unescapeLogField(match.group(3)).trim(), + LogHelper.unescapeLogField(match.group(4)).trim(), + LogHelper.unescapeLogField(match.group(5)).trim()); return logLine; } else { match = validBase.matcher(line); if (match.matches()) { LogLine logLine = new LogLine(parseDate(match.group(1).trim()), - LogManager.unescapeLogField(match.group(2)).trim(), + LogHelper.unescapeLogField(match.group(2)).trim(), null, null, null diff --git a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java index e18c4eddd7..69a3b75a28 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java @@ -19,7 +19,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -194,7 +194,7 @@ public class IPAuthentication implements AuthenticationMethod { groups.add(group); } else { - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "configuration_error", "unknown_group=" + groupName)); } @@ -202,7 +202,7 @@ public class IPAuthentication implements AuthenticationMethod { } } } catch (IPMatcherException ipme) { - log.warn(LogManager.getHeader(context, "configuration_error", + log.warn(LogHelper.getHeader(context, "configuration_error", "bad_ip=" + addr), ipme); } } @@ -228,7 +228,7 @@ public class IPAuthentication implements AuthenticationMethod { groups.remove(group); } else { - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "configuration_error", "unknown_group=" + groupName)); } @@ -236,7 +236,7 @@ public class IPAuthentication implements AuthenticationMethod { } } } catch (IPMatcherException ipme) { - log.warn(LogManager.getHeader(context, "configuration_error", + log.warn(LogHelper.getHeader(context, "configuration_error", "bad_ip=" + addr), ipme); } } @@ -248,7 +248,7 @@ public class IPAuthentication implements AuthenticationMethod { gsb.append(group.getID()).append(", "); } - log.debug(LogManager.getHeader(context, "authenticated", + log.debug(LogHelper.getHeader(context, "authenticated", "special_groups=" + gsb.toString() + " (by IP=" + addr + ", useProxies=" + useProxies.toString() + ")" )); diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index 55f4160315..16f2ec3833 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -36,7 +36,7 @@ import org.dspace.authenticate.factory.AuthenticateServiceFactory; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -156,7 +156,7 @@ public class LDAPAuthentication Group ldapGroup = groupService.findByName(context, groupName); if (ldapGroup == null) { // Oops - the group isn't there. - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "ldap_specialgroup", "Group defined in login.specialgroup does not exist")); return Collections.EMPTY_LIST; @@ -211,7 +211,7 @@ public class LDAPAuthentication String realm, HttpServletRequest request) throws SQLException { - log.info(LogManager.getHeader(context, "auth", "attempting trivial auth of user=" + netid)); + log.info(LogHelper.getHeader(context, "auth", "attempting trivial auth of user=" + netid)); // Skip out when no netid or password is given. if (netid == null || password == null) { @@ -245,7 +245,7 @@ public class LDAPAuthentication // Check a DN was found if ((dn == null) || (dn.trim().equals(""))) { - log.info(LogManager + log.info(LogHelper .getHeader(context, "failed_login", "no DN found for user " + netid)); return BAD_CREDENTIALS; } @@ -265,7 +265,7 @@ public class LDAPAuthentication // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); - log.info(LogManager + log.info(LogHelper .getHeader(context, "authenticate", "type=ldap")); return SUCCESS; } else { @@ -277,7 +277,7 @@ public class LDAPAuthentication if (ldap.ldapAuthenticate(dn, password, context)) { // Register the new user automatically - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "autoregister", "netid=" + netid)); String email = ldap.ldapEmail; @@ -290,7 +290,7 @@ public class LDAPAuthentication email = netid + configurationService.getProperty("authentication-ldap.netid_email_domain"); } else { // We don't have a valid email address. We'll default it to 'netid' but log a warning - log.warn(LogManager.getHeader(context, "autoregister", + log.warn(LogHelper.getHeader(context, "autoregister", "Unable to locate email address for account '" + netid + "', so" + " it has been set to '" + netid + "'. " + "Please check the LDAP 'email_field' OR consider " + @@ -303,7 +303,7 @@ public class LDAPAuthentication try { eperson = ePersonService.findByEmail(context, email); if (eperson != null) { - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "type=ldap-login", "type=ldap_but_already_email")); context.turnOffAuthorisationSystem(); eperson.setNetid(netid.toLowerCase()); @@ -350,12 +350,12 @@ public class LDAPAuthentication context.restoreAuthSystemState(); } - log.info(LogManager.getHeader(context, "authenticate", + log.info(LogHelper.getHeader(context, "authenticate", "type=ldap-login, created ePerson")); return SUCCESS; } else { // No auto-registration for valid certs - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "failed_login", "type=ldap_but_no_record")); return NO_SUCH_USER; } @@ -429,7 +429,7 @@ public class LDAPAuthentication } catch (NumberFormatException e) { // Log the error if it has been set but is invalid if (ldap_search_scope != null) { - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "ldap_authentication", "invalid search scope: " + ldap_search_scope)); } } @@ -548,19 +548,19 @@ public class LDAPAuthentication // Ambiguous user, can't continue } else { - log.debug(LogManager.getHeader(context, "got DN", resultDN)); + log.debug(LogHelper.getHeader(context, "got DN", resultDN)); return resultDN; } } } catch (NamingException e) { // if the lookup fails go ahead and create a new record for them because the authentication // succeeded - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "ldap_attribute_lookup", "type=failed_search " + e)); } } catch (NamingException | IOException e) { - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "ldap_authentication", "type=failed_auth " + e)); } finally { // Close the context when we're done @@ -630,7 +630,7 @@ public class LDAPAuthentication } } catch (NamingException | IOException e) { // something went wrong (like wrong password) so return false - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "ldap_authentication", "type=failed_auth " + e)); return false; } finally { @@ -714,18 +714,18 @@ public class LDAPAuthentication groupService.update(context, ldapGroup); } else { // The group does not exist - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "ldap_assignGroupsBasedOnLdapDn", "Group defined in authentication-ldap.login.groupmap." + i + " does not exist :: " + dspaceGroupName)); } } catch (AuthorizeException ae) { - log.debug(LogManager.getHeader(context, + log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not authorize addition to " + "group", dspaceGroupName)); } catch (SQLException e) { - log.debug(LogManager.getHeader(context, "assignGroupsBasedOnLdapDn could not find group", + log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group", dspaceGroupName)); } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java index 86cfb50c5f..3a7aca2746 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java @@ -17,7 +17,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -147,7 +147,7 @@ public class PasswordAuthentication .findByName(context, groupName); if (specialGroup == null) { // Oops - the group isn't there. - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "password_specialgroup", "Group defined in modules/authentication-password.cfg login" + ".specialgroup does not exist")); @@ -158,7 +158,7 @@ public class PasswordAuthentication } } } catch (Exception e) { - log.error(LogManager.getHeader(context, "getSpecialGroups", ""), e); + log.error(LogHelper.getHeader(context, "getSpecialGroups", ""), e); } return Collections.EMPTY_LIST; } @@ -196,7 +196,7 @@ public class PasswordAuthentication throws SQLException { if (username != null && password != null) { EPerson eperson = null; - log.info(LogManager.getHeader(context, "authenticate", "attempting password auth of user=" + username)); + log.info(LogHelper.getHeader(context, "authenticate", "attempting password auth of user=" + username)); eperson = EPersonServiceFactory.getInstance().getEPersonService() .findByEmail(context, username.toLowerCase()); @@ -208,7 +208,7 @@ public class PasswordAuthentication return BAD_ARGS; } else if (eperson.getRequireCertificate()) { // this user can only login with x.509 certificate - log.warn(LogManager.getHeader(context, "authenticate", + log.warn(LogHelper.getHeader(context, "authenticate", "rejecting PasswordAuthentication because " + username + " requires " + "certificate.")); return CERT_REQUIRED; @@ -216,7 +216,7 @@ public class PasswordAuthentication .checkPassword(context, eperson, password)) { // login is ok if password matches: context.setCurrentUser(eperson); - log.info(LogManager.getHeader(context, "authenticate", "type=PasswordAuthentication")); + log.info(LogHelper.getHeader(context, "authenticate", "type=PasswordAuthentication")); return SUCCESS; } else { return BAD_CREDENTIALS; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java index df9e767116..051be09cbf 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java @@ -35,7 +35,7 @@ import org.dspace.authenticate.factory.AuthenticateServiceFactory; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -286,7 +286,7 @@ public class X509Authentication implements AuthenticationMethod { try { certificate.checkValidity(); } catch (CertificateException e) { - log.info(LogManager.getHeader(context, "authentication", + log.info(LogHelper.getHeader(context, "authentication", "X.509 Certificate is EXPIRED or PREMATURE: " + e.toString())); return false; @@ -298,7 +298,7 @@ public class X509Authentication implements AuthenticationMethod { certificate.verify(caPublicKey); return true; } catch (GeneralSecurityException e) { - log.info(LogManager.getHeader(context, "authentication", + log.info(LogHelper.getHeader(context, "authentication", "X.509 Certificate FAILED SIGNATURE check: " + e.toString())); } @@ -322,11 +322,11 @@ public class X509Authentication implements AuthenticationMethod { } } log - .info(LogManager + .info(LogHelper .getHeader(context, "authentication", "Keystore method FAILED SIGNATURE check on client cert.")); } catch (GeneralSecurityException e) { - log.info(LogManager.getHeader(context, "authentication", + log.info(LogHelper.getHeader(context, "authentication", "X.509 Certificate FAILED SIGNATURE check: " + e.toString())); } @@ -461,7 +461,7 @@ public class X509Authentication implements AuthenticationMethod { if (group != null) { groups.add(group); } else { - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "configuration_error", "unknown_group=" + groupName)); } @@ -513,7 +513,7 @@ public class X509Authentication implements AuthenticationMethod { try { if (!isValid(context, certs[0])) { log - .warn(LogManager + .warn(LogHelper .getHeader(context, "authenticate", "type=x509certificate, status=BAD_CREDENTIALS (not valid)")); return BAD_CREDENTIALS; @@ -530,7 +530,7 @@ public class X509Authentication implements AuthenticationMethod { if (email != null && canSelfRegister(context, request, null)) { // Register the new user automatically - log.info(LogManager.getHeader(context, "autoregister", + log.info(LogHelper.getHeader(context, "autoregister", "from=x.509, email=" + email)); // TEMPORARILY turn off authorisation @@ -549,25 +549,25 @@ public class X509Authentication implements AuthenticationMethod { } else { // No auto-registration for valid certs log - .warn(LogManager + .warn(LogHelper .getHeader(context, "authenticate", "type=cert_but_no_record, cannot auto-register")); return NO_SUCH_USER; } } else if (!eperson.canLogIn()) { // make sure this is a login account - log.warn(LogManager.getHeader(context, "authenticate", + log.warn(LogHelper.getHeader(context, "authenticate", "type=x509certificate, email=" + email + ", canLogIn=false, rejecting.")); return BAD_ARGS; } else { - log.info(LogManager.getHeader(context, "login", + log.info(LogHelper.getHeader(context, "login", "type=x509certificate")); context.setCurrentUser(eperson); setSpecialGroupsFlag(request, email); return SUCCESS; } } catch (AuthorizeException ce) { - log.warn(LogManager.getHeader(context, "authorize_exception", + log.warn(LogHelper.getHeader(context, "authorize_exception", ""), ce); } diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthorityValueServiceImpl.java b/dspace-api/src/main/java/org/dspace/authority/AuthorityValueServiceImpl.java index fc62f0df19..70005ecbee 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthorityValueServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthorityValueServiceImpl.java @@ -21,7 +21,7 @@ import org.apache.solr.common.SolrDocument; import org.dspace.authority.service.AuthorityValueService; import org.dspace.content.authority.SolrAuthority; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.springframework.beans.factory.annotation.Autowired; /** @@ -220,7 +220,7 @@ public class AuthorityValueServiceImpl implements AuthorityValueService { } } } catch (Exception e) { - log.error(LogManager.getHeader(context, "Error while retrieving AuthorityValue from solr", + log.error(LogHelper.getHeader(context, "Error while retrieving AuthorityValue from solr", "query: " + queryString), e); } diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index 302d46eb0d..998c31cf20 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -17,7 +17,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.sort.OrderFormat; import org.dspace.sort.SortOption; @@ -85,7 +85,7 @@ public class BrowseEngine { */ public BrowseInfo browse(BrowserScope bs) throws BrowseException { - log.debug(LogManager.getHeader(context, "browse", "")); + log.debug(LogHelper.getHeader(context, "browse", "")); // first, load the browse scope into the object this.scope = bs; @@ -119,7 +119,7 @@ public class BrowseEngine { */ public BrowseInfo browseMini(BrowserScope bs) throws BrowseException { - log.info(LogManager.getHeader(context, "browse_mini", "")); + log.info(LogHelper.getHeader(context, "browse_mini", "")); // load the scope into the object this.scope = bs; @@ -198,7 +198,7 @@ public class BrowseEngine { */ private BrowseInfo browseByItem(BrowserScope bs) throws BrowseException { - log.info(LogManager.getHeader(context, "browse_by_item", "")); + log.info(LogHelper.getHeader(context, "browse_by_item", "")); try { // get the table name that we are going to be getting our data from dao.setTable(browseIndex.getTableName()); @@ -374,7 +374,7 @@ public class BrowseEngine { */ private BrowseInfo browseByValue(BrowserScope bs) throws BrowseException { - log.info(LogManager.getHeader(context, "browse_by_value", "focus=" + bs.getJumpToValue())); + log.info(LogHelper.getHeader(context, "browse_by_value", "focus=" + bs.getJumpToValue())); try { // get the table name that we are going to be getting our data from @@ -518,17 +518,17 @@ public class BrowseEngine { */ private String getJumpToValue() throws BrowseException { - log.debug(LogManager.getHeader(context, "get_focus_value", "")); + log.debug(LogHelper.getHeader(context, "get_focus_value", "")); // if the focus is by value, just return it if (scope.hasJumpToValue()) { - log.debug(LogManager.getHeader(context, "get_focus_value_return", "return=" + scope.getJumpToValue())); + log.debug(LogHelper.getHeader(context, "get_focus_value_return", "return=" + scope.getJumpToValue())); return scope.getJumpToValue(); } // if the focus is to start with, then we need to return the value of the starts with if (scope.hasStartsWith()) { - log.debug(LogManager.getHeader(context, "get_focus_value_return", "return=" + scope.getStartsWith())); + log.debug(LogHelper.getHeader(context, "get_focus_value_return", "return=" + scope.getStartsWith())); return scope.getStartsWith(); } @@ -565,7 +565,7 @@ public class BrowseEngine { // item (I think) String max = dao.doMaxQuery(col, tableName, id); - log.debug(LogManager.getHeader(context, "get_focus_value_return", "return=" + max)); + log.debug(LogHelper.getHeader(context, "get_focus_value_return", "return=" + max)); return max; } @@ -671,7 +671,7 @@ public class BrowseEngine { */ private int getTotalResults(boolean distinct) throws SQLException, BrowseException { - log.debug(LogManager.getHeader(context, "get_total_results", "distinct=" + distinct)); + log.debug(LogHelper.getHeader(context, "get_total_results", "distinct=" + distinct)); // tell the browse query whether we are distinct dao.setDistinct(distinct); @@ -706,7 +706,7 @@ public class BrowseEngine { dao.setOffset(offset); dao.setCountValues(null); - log.debug(LogManager.getHeader(context, "get_total_results_return", "return=" + count)); + log.debug(LogHelper.getHeader(context, "get_total_results_return", "return=" + count)); return count; } diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamFormatServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamFormatServiceImpl.java index 89bf74ece6..fa5932ded1 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamFormatServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamFormatServiceImpl.java @@ -18,7 +18,7 @@ import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.BitstreamFormatDAO; import org.dspace.content.service.BitstreamFormatService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.springframework.beans.factory.annotation.Autowired; /** @@ -68,7 +68,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService { if (bitstreamFormat == null) { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, + log.debug(LogHelper.getHeader(context, "find_bitstream_format", "not_found,bitstream_format_id=" + id)); } @@ -78,7 +78,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService { // not null, return format object if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_bitstream_format", + log.debug(LogHelper.getHeader(context, "find_bitstream_format", "bitstream_format_id=" + id)); } @@ -129,7 +129,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService { BitstreamFormat bitstreamFormat = bitstreamFormatDAO.create(context, new BitstreamFormat()); - log.info(LogManager.getHeader(context, "create_bitstream_format", + log.info(LogHelper.getHeader(context, "create_bitstream_format", "bitstream_format_id=" + bitstreamFormat.getID())); @@ -189,7 +189,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService { } for (BitstreamFormat bitstreamFormat : bitstreamFormats) { - log.info(LogManager.getHeader(context, "update_bitstream_format", + log.info(LogHelper.getHeader(context, "update_bitstream_format", "bitstream_format_id=" + bitstreamFormat.getID())); bitstreamFormatDAO.save(context, bitstreamFormat); @@ -218,7 +218,7 @@ public class BitstreamFormatServiceImpl implements BitstreamFormatService { // Delete this format from database bitstreamFormatDAO.delete(context, bitstreamFormat); - log.info(LogManager.getHeader(context, "delete_bitstream_format", + log.info(LogHelper.getHeader(context, "delete_bitstream_format", "bitstream_format_id=" + bitstreamFormat.getID() + ",bitstreams_changed=" + numberChanged)); } diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 98760a43fe..467ec38b74 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -28,7 +28,7 @@ import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.event.Event; import org.dspace.storage.bitstore.service.BitstreamStorageService; import org.springframework.beans.factory.annotation.Autowired; @@ -73,7 +73,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp if (bitstream == null) { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_bitstream", + log.debug(LogHelper.getHeader(context, "find_bitstream", "not_found,bitstream_id=" + id)); } @@ -82,7 +82,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp // not null, return Bitstream if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_bitstream", + log.debug(LogHelper.getHeader(context, "find_bitstream", "bitstream_id=" + id)); } @@ -131,7 +131,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp // Store the bits UUID bitstreamID = bitstreamStorageService.store(context, bitstreamDAO.create(context, new Bitstream()), is); - log.info(LogManager.getHeader(context, "create_bitstream", + log.info(LogHelper.getHeader(context, "create_bitstream", "bitstream_id=" + bitstreamID)); // Set the format to "unknown" @@ -191,7 +191,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp bitstreamStorageService.register( context, bitstream, assetstore, bitstreamPath); - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "create_bitstream", "bitstream_id=" + bitstream.getID())); @@ -248,7 +248,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp // Check authorisation authorizeService.authorizeAction(context, bitstream, Constants.WRITE); - log.info(LogManager.getHeader(context, "update_bitstream", + log.info(LogHelper.getHeader(context, "update_bitstream", "bitstream_id=" + bitstream.getID())); super.update(context, bitstream); if (bitstream.isModified()) { @@ -273,7 +273,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp // changed to a check on delete // Check authorisation authorizeService.authorizeAction(context, bitstream, Constants.DELETE); - log.info(LogManager.getHeader(context, "delete_bitstream", + log.info(LogHelper.getHeader(context, "delete_bitstream", "bitstream_id=" + bitstream.getID())); context.addEvent(new Event(Event.DELETE, Constants.BITSTREAM, bitstream.getID(), diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 7605b6f399..aa32983362 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -33,7 +33,7 @@ import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.event.Event; import org.springframework.beans.factory.annotation.Autowired; @@ -73,14 +73,14 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement Bundle bundle = bundleDAO.findByID(context, Bundle.class, id); if (bundle == null) { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_bundle", + log.debug(LogHelper.getHeader(context, "find_bundle", "not_found,bundle_id=" + id)); } return null; } else { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_bundle", + log.debug(LogHelper.getHeader(context, "find_bundle", "bundle_id=" + id)); } @@ -105,7 +105,7 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement } - log.info(LogManager.getHeader(context, "create_bundle", "bundle_id=" + log.info(LogHelper.getHeader(context, "create_bundle", "bundle_id=" + bundle.getID())); // if we ever use the identifier service for bundles, we should @@ -136,7 +136,7 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement // Check authorisation authorizeService.authorizeAction(context, bundle, Constants.ADD); - log.info(LogManager.getHeader(context, "add_bitstream", "bundle_id=" + log.info(LogHelper.getHeader(context, "add_bitstream", "bundle_id=" + bundle.getID() + ",bitstream_id=" + bitstream.getID())); // First check that the bitstream isn't already in the list @@ -177,7 +177,7 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement // Check authorisation authorizeService.authorizeAction(context, bundle, Constants.REMOVE); - log.info(LogManager.getHeader(context, "remove_bitstream", + log.info(LogHelper.getHeader(context, "remove_bitstream", "bundle_id=" + bundle.getID() + ",bitstream_id=" + bitstream.getID())); @@ -362,14 +362,14 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement // If we have an invalid Bitstream ID, just ignore it, but log a warning if (bitstream == null) { //This should never occur but just in case - log.warn(LogManager.getHeader(context, "Invalid bitstream id while changing bitstream order", + log.warn(LogHelper.getHeader(context, "Invalid bitstream id while changing bitstream order", "Bundle: " + bundle.getID() + ", bitstream id: " + bitstreamId)); continue; } // If we have a Bitstream not in the current list, log a warning & exit immediately if (!currentBitstreams.contains(bitstream)) { - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "Encountered a bitstream not in this bundle while changing bitstream " + "order. Bitstream order will not be changed.", "Bundle: " + bundle.getID() + ", bitstream id: " + bitstreamId)); @@ -380,7 +380,7 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement // If our lists are different sizes, exit immediately if (updatedBitstreams.size() != currentBitstreams.size()) { - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "Size of old list and new list do not match. Bitstream order will not be " + "changed.", "Bundle: " + bundle.getID())); @@ -471,7 +471,7 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement public void update(Context context, Bundle bundle) throws SQLException, AuthorizeException { // Check authorisation //AuthorizeManager.authorizeAction(ourContext, this, Constants.WRITE); - log.info(LogManager.getHeader(context, "update_bundle", "bundle_id=" + log.info(LogHelper.getHeader(context, "update_bundle", "bundle_id=" + bundle.getID())); super.update(context, bundle); @@ -491,7 +491,7 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement @Override public void delete(Context context, Bundle bundle) throws SQLException, AuthorizeException, IOException { - log.info(LogManager.getHeader(context, "delete_bundle", "bundle_id=" + log.info(LogHelper.getHeader(context, "delete_bundle", "bundle_id=" + bundle.getID())); authorizeService.authorizeAction(context, bundle, Constants.DELETE); 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 380c0336af..902f84b3ee 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -39,7 +39,7 @@ import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.I18nUtil; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.service.LicenseService; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; @@ -167,7 +167,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i newCollection.getID(), newCollection.getHandle(), getIdentifiers(context, newCollection))); - log.info(LogManager.getHeader(context, "create_collection", + log.info(LogHelper.getHeader(context, "create_collection", "collection_id=" + newCollection.getID()) + ",handle=" + newCollection.getHandle()); @@ -345,7 +345,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i if (is == null) { collection.setLogo(null); - log.info(LogManager.getHeader(context, "remove_logo", + log.info(LogHelper.getHeader(context, "remove_logo", "collection_id=" + collection.getID())); } else { Bitstream newLogo = bitstreamService.create(context, is); @@ -357,7 +357,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i .getPoliciesActionFilter(context, collection, Constants.READ); authorizeService.addPolicies(context, policies, newLogo); - log.info(LogManager.getHeader(context, "set_logo", + log.info(LogHelper.getHeader(context, "set_logo", "collection_id=" + collection.getID() + "logo_bitstream_id=" + newLogo.getID())); } @@ -393,7 +393,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i try { workflow = workflowFactory.getWorkflow(collection); } catch (WorkflowConfigurationException e) { - log.error(LogManager.getHeader(context, "setWorkflowGroup", + log.error(LogHelper.getHeader(context, "setWorkflowGroup", "collection_id=" + collection.getID() + " " + e.getMessage()), e); } if (!StringUtils.equals(workflowFactory.getDefaultWorkflow().getID(), workflow.getID())) { @@ -569,7 +569,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i Item template = itemService.createTemplateItem(context, collection); collection.setTemplateItem(template); - log.info(LogManager.getHeader(context, "create_template_item", + log.info(LogHelper.getHeader(context, "create_template_item", "collection_id=" + collection.getID() + ",template_item_id=" + template.getID())); } @@ -584,7 +584,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i Item template = collection.getTemplateItem(); if (template != null) { - log.info(LogManager.getHeader(context, "remove_template_item", + log.info(LogHelper.getHeader(context, "remove_template_item", "collection_id=" + collection.getID() + ",template_item_id=" + template.getID())); // temporarily turn off auth system, we have already checked the permission on the top of the method @@ -604,7 +604,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i // Check authorisation authorizeService.authorizeAction(context, collection, Constants.ADD); - log.info(LogManager.getHeader(context, "add_item", "collection_id=" + log.info(LogHelper.getHeader(context, "add_item", "collection_id=" + collection.getID() + ",item_id=" + item.getID())); // Create mapping @@ -645,7 +645,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i // Check authorisation canEdit(context, collection, true); - log.info(LogManager.getHeader(context, "update_collection", + log.info(LogHelper.getHeader(context, "update_collection", "collection_id=" + collection.getID())); super.update(context, collection); @@ -702,7 +702,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i @Override public void delete(Context context, Collection collection) throws SQLException, AuthorizeException, IOException { - log.info(LogManager.getHeader(context, "delete_collection", + log.info(LogHelper.getHeader(context, "delete_collection", "collection_id=" + collection.getID())); // remove harvested collections. 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 73b1c062fd..a120c03a97 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -33,7 +33,7 @@ import org.dspace.content.service.SiteService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.I18nUtil; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; import org.dspace.event.Event; @@ -128,7 +128,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp getIdentifiers(context, newCommunity))); } - log.info(LogManager.getHeader(context, "create_community", + log.info(LogHelper.getHeader(context, "create_community", "community_id=" + newCommunity.getID()) + ",handle=" + newCommunity.getHandle()); @@ -218,7 +218,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp // First, delete any existing logo Bitstream oldLogo = community.getLogo(); if (oldLogo != null) { - log.info(LogManager.getHeader(context, "remove_logo", + log.info(LogHelper.getHeader(context, "remove_logo", "community_id=" + community.getID())); community.setLogo(null); bitstreamService.delete(context, oldLogo); @@ -234,7 +234,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp .getPoliciesActionFilter(context, community, Constants.READ); authorizeService.addPolicies(context, policies, newLogo); - log.info(LogManager.getHeader(context, "set_logo", + log.info(LogHelper.getHeader(context, "set_logo", "community_id=" + community.getID() + "logo_bitstream_id=" + newLogo.getID())); } @@ -247,7 +247,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp // Check authorisation canEdit(context, community); - log.info(LogManager.getHeader(context, "update_community", + log.info(LogHelper.getHeader(context, "update_community", "community_id=" + community.getID())); super.update(context, community); @@ -365,7 +365,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp // Check authorisation authorizeService.authorizeAction(context, community, Constants.ADD); - log.info(LogManager.getHeader(context, "add_collection", + log.info(LogHelper.getHeader(context, "add_collection", "community_id=" + community.getID() + ",collection_id=" + collection.getID())); if (!community.getCollections().contains(collection)) { @@ -401,7 +401,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp // Check authorisation authorizeService.authorizeAction(context, parentCommunity, Constants.ADD); - log.info(LogManager.getHeader(context, "add_subcommunity", + log.info(LogHelper.getHeader(context, "add_subcommunity", "parent_comm_id=" + parentCommunity.getID() + ",child_comm_id=" + childCommunity .getID())); @@ -431,7 +431,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp collection.removeCommunity(community); } - log.info(LogManager.getHeader(context, "remove_collection", + log.info(LogHelper.getHeader(context, "remove_collection", "community_id=" + community.getID() + ",collection_id=" + collection.getID())); // Remove any mappings @@ -451,7 +451,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp rawDelete(context, childCommunity); - log.info(LogManager.getHeader(context, "remove_subcommunity", + log.info(LogHelper.getHeader(context, "remove_subcommunity", "parent_comm_id=" + parentCommunity.getID() + ",child_comm_id=" + childCommunity .getID())); @@ -519,7 +519,7 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp */ protected void rawDelete(Context context, Community community) throws SQLException, AuthorizeException, IOException { - log.info(LogManager.getHeader(context, "delete_community", + log.info(LogHelper.getHeader(context, "delete_community", "community_id=" + community.getID())); context.addEvent(new Event(Event.DELETE, Constants.COMMUNITY, community.getID(), community.getHandle(), 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 59beec72a6..fd9a72f074 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -47,7 +47,7 @@ import org.dspace.content.service.WorkspaceItemService; import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.event.Event; @@ -159,7 +159,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It Item item = itemDAO.findByID(context, Item.class, id); if (item == null) { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_item", + log.debug(LogHelper.getHeader(context, "find_item", "not_found,item_id=" + id)); } return null; @@ -167,7 +167,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // not null, return item if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_item", "item_id=" + log.debug(LogHelper.getHeader(context, "find_item", "item_id=" + id)); } @@ -184,7 +184,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It workspaceItem.setItem(item); - log.info(LogManager.getHeader(context, "create_item", "item_id=" + log.info(LogHelper.getHeader(context, "create_item", "item_id=" + item.getID())); return item; @@ -202,7 +202,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It collection.setTemplateItem(template); template.setTemplateItemOf(collection); - log.info(LogManager.getHeader(context, "create_template_item", + log.info(LogHelper.getHeader(context, "create_template_item", "collection_id=" + collection.getID() + ",template_item_id=" + template.getID())); @@ -340,7 +340,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // Check authorisation authorizeService.authorizeAction(context, item, Constants.ADD); - log.info(LogManager.getHeader(context, "add_bundle", "item_id=" + log.info(LogHelper.getHeader(context, "add_bundle", "item_id=" + item.getID() + ",bundle_id=" + bundle.getID())); // Check it's not already there @@ -368,7 +368,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // Check authorisation authorizeService.authorizeAction(context, item, Constants.REMOVE); - log.info(LogManager.getHeader(context, "remove_bundle", "item_id=" + log.info(LogHelper.getHeader(context, "remove_bundle", "item_id=" + item.getID() + ",bundle_id=" + bundle.getID())); context.addEvent(new Event(Event.REMOVE, Constants.ITEM, item.getID(), @@ -432,7 +432,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It context.addEvent(new Event(Event.CREATE, Constants.ITEM, item.getID(), null, getIdentifiers(context, item))); - log.info(LogManager.getHeader(context, "create_item", "item_id=" + item.getID())); + log.info(LogHelper.getHeader(context, "create_item", "item_id=" + item.getID())); return item; } @@ -490,7 +490,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It authorizeService.authorizeAction(context, item, Constants.WRITE); } - log.info(LogManager.getHeader(context, "update_item", "item_id=" + log.info(LogHelper.getHeader(context, "update_item", "item_id=" + item.getID())); super.update(context, item); @@ -595,7 +595,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It } // Write log - log.info(LogManager.getHeader(context, "withdraw_item", "user=" + log.info(LogHelper.getHeader(context, "withdraw_item", "user=" + e.getEmail() + ",item_id=" + item.getID())); } @@ -661,7 +661,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It } // Write log - log.info(LogManager.getHeader(context, "reinstate_item", "user=" + log.info(LogHelper.getHeader(context, "reinstate_item", "user=" + e.getEmail() + ",item_id=" + item.getID())); } @@ -682,7 +682,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It context.addEvent(new Event(Event.DELETE, Constants.ITEM, item.getID(), item.getHandle(), getIdentifiers(context, item))); - log.info(LogManager.getHeader(context, "delete_item", "item_id=" + log.info(LogHelper.getHeader(context, "delete_item", "item_id=" + item.getID())); // Remove relationships @@ -731,7 +731,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It bundleService.delete(context, b); - log.info(LogManager.getHeader(context, "remove_bundle", "item_id=" + log.info(LogHelper.getHeader(context, "remove_bundle", "item_id=" + item.getID() + ",bundle_id=" + b.getID())); context .addEvent(new Event(Event.REMOVE, Constants.ITEM, item.getID(), Constants.BUNDLE, b.getID(), b.getName())); @@ -802,7 +802,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It adjustItemPolicies(context, item, collection); adjustBundleBitstreamPolicies(context, item, collection); - log.debug(LogManager.getHeader(context, "item_inheritCollectionDefaultPolicies", + log.debug(LogHelper.getHeader(context, "item_inheritCollectionDefaultPolicies", "item_id=" + item.getID())); } @@ -890,7 +890,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // If we are moving from the owning collection, update that too if (isOwningCollection(item, from)) { // Update the owning collection - log.info(LogManager.getHeader(context, "move_item", + log.info(LogHelper.getHeader(context, "move_item", "item_id=" + item.getID() + ", from " + "collection_id=" + from.getID() + " to " + "collection_id=" + to.getID())); @@ -898,7 +898,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // If applicable, update the item policies if (inheritDefaultPolicies) { - log.info(LogManager.getHeader(context, "move_item", + log.info(LogHelper.getHeader(context, "move_item", "Updating item with inherited policies")); inheritCollectionDefaultPolicies(context, item, to); } diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataFieldServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataFieldServiceImpl.java index 569b5840c6..254cff0266 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataFieldServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataFieldServiceImpl.java @@ -25,7 +25,7 @@ import org.dspace.content.service.MetadataValueService; import org.dspace.content.service.SiteService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.discovery.indexobject.IndexableMetadataField; import org.dspace.event.Event; import org.springframework.beans.factory.annotation.Autowired; @@ -83,7 +83,7 @@ public class MetadataFieldServiceImpl implements MetadataFieldService { metadataField = metadataFieldDAO.create(context, metadataField); metadataFieldDAO.save(context, metadataField); - log.info(LogManager.getHeader(context, "create_metadata_field", + log.info(LogHelper.getHeader(context, "create_metadata_field", "metadata_field_id=" + metadataField.getID())); // Update the index of type metadatafield this.triggerEventToUpdateIndex(context, metadataField.getID()); @@ -155,7 +155,7 @@ public class MetadataFieldServiceImpl implements MetadataFieldService { metadataFieldDAO.save(context, metadataField); - log.info(LogManager.getHeader(context, "update_metadatafieldregistry", + log.info(LogHelper.getHeader(context, "update_metadatafieldregistry", "metadata_field_id=" + metadataField.getID() + "element=" + metadataField .getElement() + "qualifier=" + metadataField.getQualifier())); @@ -187,7 +187,7 @@ public class MetadataFieldServiceImpl implements MetadataFieldService { .toString() + " cannot be deleted as it is currently used by one or more objects."); } - log.info(LogManager.getHeader(context, "delete_metadata_field", + log.info(LogHelper.getHeader(context, "delete_metadata_field", "metadata_field_id=" + metadataField.getID())); // Update the index of type metadatafield this.triggerEventToUpdateIndex(context, metadataField.getID()); diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataSchemaServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataSchemaServiceImpl.java index d5c2c22f88..2eeb57a395 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataSchemaServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataSchemaServiceImpl.java @@ -17,7 +17,7 @@ import org.dspace.content.dao.MetadataSchemaDAO; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.springframework.beans.factory.annotation.Autowired; /** @@ -74,7 +74,7 @@ public class MetadataSchemaServiceImpl implements MetadataSchemaService { metadataSchema.setNamespace(namespace); metadataSchema.setName(name); metadataSchemaDAO.save(context, metadataSchema); - log.info(LogManager.getHeader(context, "create_metadata_schema", + log.info(LogHelper.getHeader(context, "create_metadata_schema", "metadata_schema_id=" + metadataSchema.getID())); return metadataSchema; @@ -106,7 +106,7 @@ public class MetadataSchemaServiceImpl implements MetadataSchemaService { + " unique"); } metadataSchemaDAO.save(context, metadataSchema); - log.info(LogManager.getHeader(context, "update_metadata_schema", + log.info(LogHelper.getHeader(context, "update_metadata_schema", "metadata_schema_id=" + metadataSchema.getID() + "namespace=" + metadataSchema.getNamespace() + "name=" + metadataSchema.getName())); } @@ -125,7 +125,7 @@ public class MetadataSchemaServiceImpl implements MetadataSchemaService { metadataSchemaDAO.delete(context, metadataSchema); - log.info(LogManager.getHeader(context, "delete_metadata_schema", + log.info(LogHelper.getHeader(context, "delete_metadata_schema", "metadata_schema_id=" + metadataSchema.getID())); } diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java index 2451e6a8e6..78bfd40b28 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java @@ -21,7 +21,7 @@ import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.MetadataValueService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.springframework.beans.factory.annotation.Autowired; /** @@ -80,7 +80,7 @@ public class MetadataValueServiceImpl implements MetadataValueService { @Override public void update(Context context, MetadataValue metadataValue) throws SQLException { metadataValueDAO.save(context, metadataValue); - log.info(LogManager.getHeader(context, "update_metadatavalue", + log.info(LogHelper.getHeader(context, "update_metadatavalue", "metadata_value_id=" + metadataValue.getID())); } @@ -102,7 +102,7 @@ public class MetadataValueServiceImpl implements MetadataValueService { @Override public void delete(Context context, MetadataValue metadataValue) throws SQLException { - log.info(LogManager.getHeader(context, "delete_metadata_value", + log.info(LogHelper.getHeader(context, "delete_metadata_value", " metadata_value_id=" + metadataValue.getID())); metadataValueDAO.delete(context, metadataValue); } diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index 1b419da816..5af3f141bd 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -339,7 +339,7 @@ public class RelationshipServiceImpl implements RelationshipService { @Override public void delete(Context context, Relationship relationship, boolean copyToLeftItem, boolean copyToRightItem) throws SQLException, AuthorizeException { - log.info(org.dspace.core.LogManager.getHeader(context, "delete_relationship", + log.info(org.dspace.core.LogHelper.getHeader(context, "delete_relationship", "relationship_id=" + relationship.getID() + "&" + "copyMetadataValuesToLeftItem=" + copyToLeftItem + "&" + "copyMetadataValuesToRightItem=" + copyToRightItem)); @@ -356,7 +356,7 @@ public class RelationshipServiceImpl implements RelationshipService { @Override public void forceDelete(Context context, Relationship relationship, boolean copyToLeftItem, boolean copyToRightItem) throws SQLException, AuthorizeException { - log.info(org.dspace.core.LogManager.getHeader(context, "delete_relationship", + log.info(org.dspace.core.LogHelper.getHeader(context, "delete_relationship", "relationship_id=" + relationship.getID() + "&" + "copyMetadataValuesToLeftItem=" + copyToLeftItem + "&" + "copyMetadataValuesToRightItem=" + copyToRightItem)); 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 8fc302f8bf..7675d298d6 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -25,7 +25,7 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.event.Event; import org.dspace.workflow.WorkflowItem; @@ -66,12 +66,12 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { if (workspaceItem == null) { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_workspace_item", + log.debug(LogHelper.getHeader(context, "find_workspace_item", "not_found,workspace_item_id=" + id)); } } else { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_workspace_item", + log.debug(LogHelper.getHeader(context, "find_workspace_item", "workspace_item_id=" + id)); } } @@ -126,7 +126,7 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { itemService.update(context, item); workspaceItem.setItem(item); - log.info(LogManager.getHeader(context, "create_workspace_item", + log.info(LogHelper.getHeader(context, "create_workspace_item", "workspace_item_id=" + workspaceItem.getID() + "item_id=" + item.getID() + "collection_id=" + collection.getID())); @@ -191,7 +191,7 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { public void update(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException { // Authorisation is checked by the item.update() method below - log.info(LogManager.getHeader(context, "update_workspace_item", + log.info(LogHelper.getHeader(context, "update_workspace_item", "workspace_item_id=" + workspaceItem.getID())); // Update the item @@ -219,7 +219,7 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { + "original submitter to delete a workspace item"); } - log.info(LogManager.getHeader(context, "delete_workspace_item", + log.info(LogHelper.getHeader(context, "delete_workspace_item", "workspace_item_id=" + workspaceItem.getID() + "item_id=" + item.getID() + "collection_id=" + workspaceItem.getCollection().getID())); @@ -256,7 +256,7 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { Item item = workspaceItem.getItem(); authorizeService.authorizeAction(context, item, Constants.WRITE); - log.info(LogManager.getHeader(context, "delete_workspace_item", + log.info(LogHelper.getHeader(context, "delete_workspace_item", "workspace_item_id=" + workspaceItem.getID() + "item_id=" + item.getID() + "collection_id=" + workspaceItem.getCollection().getID())); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java index 97d674fd12..2f90dae354 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java @@ -75,7 +75,7 @@ import org.dspace.content.service.BitstreamService; import org.dspace.content.service.SiteService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.Utils; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.core.service.PluginService; @@ -265,7 +265,7 @@ public abstract class AbstractMETSDisseminator } //end if/else // Assuming no errors, log this dissemination - log.info(LogManager.getHeader(context, "package_disseminate", + log.info(LogHelper.getHeader(context, "package_disseminate", "Disseminated package file=" + pkgFile.getName() + " for Object, type=" + Constants.typeText[dso.getType()] + ", handle=" diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 0db9a98b9a..67e20581ef 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -43,7 +43,7 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; @@ -210,7 +210,7 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { DSpaceObject dso = null; try { - log.info(LogManager.getHeader(context, "package_parse", + log.info(LogHelper.getHeader(context, "package_parse", "Parsing package for ingest, file=" + pkgFile.getName())); // Parse our ingest package, extracting out the METS manifest in the @@ -257,7 +257,7 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { if (params.restoreModeEnabled()) { action = "package_restore"; } - log.info(LogManager.getHeader(context, action, + log.info(LogHelper.getHeader(context, action, "Created new Object, type=" + Constants.typeText[dso.getType()] + ", handle=" + dso.getHandle() + ", dbID=" @@ -387,7 +387,7 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { //If user specified to skip item ingest if any "missing parent" error message occur if (params.getBooleanProperty("skipIfParentMissing", false)) { //log a warning instead of throwing an error - log.warn(LogManager.getHeader(context, "package_ingest", + log.warn(LogHelper.getHeader(context, "package_ingest", "SKIPPING ingest of object '" + manifest.getObjID() + "' as parent DSpace Object could not be found. " + "If you are running a recursive ingest, it is likely this " + @@ -1025,7 +1025,7 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { DSpaceObject dso = null; try { - log.info(LogManager.getHeader(context, "package_parse", + log.info(LogHelper.getHeader(context, "package_parse", "Parsing package for replace, file=" + pkgFile.getName())); // Parse our ingest package, extracting out the METS manifest in the @@ -1077,7 +1077,7 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { //if ingestion was successful if (dso != null) { // Log that we created an object - log.info(LogManager.getHeader(context, "package_replace", + log.info(LogHelper.getHeader(context, "package_replace", "Created new Object, type=" + Constants.typeText[dso.getType()] + ", handle=" + dso.getHandle() + ", dbID=" @@ -1093,7 +1093,7 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { params, null); // Log that we replaced an object - log.info(LogManager.getHeader(context, "package_replace", + log.info(LogHelper.getHeader(context, "package_replace", "Replaced Object, type=" + Constants.typeText[dso.getType()] + ", handle=" + dso.getHandle() + ", dbID=" diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractPackageIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractPackageIngester.java index da58f1cf0b..f66b2f2b76 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractPackageIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractPackageIngester.java @@ -27,7 +27,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.workflow.WorkflowException; @@ -147,7 +147,7 @@ public abstract class AbstractPackageIngester //if we are skipping over (i.e. keeping) existing objects if (params.keepExistingModeEnabled()) { - log.warn(LogManager.getHeader(context, "skip_package_ingest", + log.warn(LogHelper.getHeader(context, "skip_package_ingest", "Object already exists, package-skipped=" + pkgFile.getName())); } else { // Pass this exception on -- which essentially causes a full rollback of all changes (this is @@ -156,7 +156,7 @@ public abstract class AbstractPackageIngester } } } else { - log.info(LogManager.getHeader(context, "skip_package_ingest", + log.info(LogHelper.getHeader(context, "skip_package_ingest", "Object was already ingested, package-skipped=" + pkgFile.getName())); } @@ -274,7 +274,7 @@ public abstract class AbstractPackageIngester // the object to be replaced from the package itself. replacedDso = replace(context, dso, pkgFile, params); } else { - log.info(LogManager.getHeader(context, "skip_package_replace", + log.info(LogHelper.getHeader(context, "skip_package_replace", "Object was already replaced, package-skipped=" + pkgFile.getName())); } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java b/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java index c6036d2261..6c7baad454 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java @@ -45,7 +45,7 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.SelfNamedPlugin; import org.dspace.core.Utils; import org.dspace.workflow.WorkflowException; @@ -166,7 +166,7 @@ public class PDFPackager workspaceItemService.update(context, wi); success = true; - log.info(LogManager.getHeader(context, "ingest", + log.info(LogHelper.getHeader(context, "ingest", "Created new Item, db ID=" + String.valueOf(myitem.getID()) + ", WorkspaceItem ID=" + String.valueOf(wi.getID()))); diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 0b2f18aec8..9ba36270ce 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -313,7 +313,7 @@ public class Context implements AutoCloseable { try { previousState = authStateChangeHistory.pop(); } catch (EmptyStackException ex) { - log.warn(LogManager.getHeader(this, "restore_auth_sys_state", + log.warn(LogHelper.getHeader(this, "restore_auth_sys_state", "not previous state info available " + ex.getLocalizedMessage())); previousState = Boolean.FALSE; @@ -328,7 +328,7 @@ public class Context implements AutoCloseable { // if previousCaller is not the current caller *only* log a warning if (!previousCaller.equals(caller)) { log - .warn(LogManager + .warn(LogHelper .getHeader( this, "restore_auth_sys_state", diff --git a/dspace-api/src/main/java/org/dspace/core/LogManager.java b/dspace-api/src/main/java/org/dspace/core/LogHelper.java similarity index 98% rename from dspace-api/src/main/java/org/dspace/core/LogManager.java rename to dspace-api/src/main/java/org/dspace/core/LogHelper.java index c8988ca997..00cc0f2766 100644 --- a/dspace-api/src/main/java/org/dspace/core/LogManager.java +++ b/dspace-api/src/main/java/org/dspace/core/LogHelper.java @@ -16,12 +16,12 @@ import org.dspace.eperson.EPerson; * @author Robert Tansley * @version $Revision$ */ -public class LogManager { +public class LogHelper { /** * Default constructor */ - private LogManager() { } + private LogHelper() { } /** * Generate the log header diff --git a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java index 4eb7ec5348..05c7a8d999 100644 --- a/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/XmlWorkflowCuratorServiceImpl.java @@ -19,7 +19,7 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.curate.service.XmlWorkflowCuratorService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -107,7 +107,7 @@ public class XmlWorkflowCuratorServiceImpl if (wfi != null) { return curate(curator, c, wfi); } else { - LOG.warn(LogManager.getHeader(c, "No workflow item found for id: {}", null), wfId); + LOG.warn(LogHelper.getHeader(c, "No workflow item found for id: {}", null), wfId); } return false; } 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 0791824085..84aca869cb 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -59,7 +59,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Email; import org.dspace.core.I18nUtil; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationParameters; import org.dspace.discovery.configuration.DiscoveryMoreLikeThisConfiguration; @@ -153,7 +153,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { getIndexableObjectFactory(indexableObject); if (force || requiresIndexing(indexableObject.getUniqueIndexID(), indexableObject.getLastModified())) { update(context, indexableObjectFactory, indexableObject); - log.info(LogManager.getHeader(context, "indexed_object", indexableObject.getUniqueIndexID())); + log.info(LogHelper.getHeader(context, "indexed_object", indexableObject.getUniqueIndexID())); } } catch (IOException | SQLException | SolrServerException | SearchServiceException e) { log.error(e.getMessage(), e); @@ -890,7 +890,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { result.addIndexableObject(indexableObject); } else { // log has warn because we try to fix the issue - log.warn(LogManager.getHeader(context, + log.warn(LogHelper.getHeader(context, "Stale entry found in Discovery index," + " as we could not find the DSpace object it refers to. ", "Unique identifier: " + doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID))); @@ -1116,7 +1116,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { } catch (IOException | SQLException | SolrServerException e) { // Any acception that we get ignore it. // We do NOT want any crashed to shown by the user - log.error(LogManager.getHeader(context, "Error while quering solr", "Query: " + query), e); + log.error(LogHelper.getHeader(context, "Error while quering solr", "Query: " + query), e); return new ArrayList<>(0); } } @@ -1224,8 +1224,7 @@ public class SolrServiceImpl implements SearchService, IndexingService { } } } catch (IOException | SQLException | SolrServerException e) { - log.error( - LogManager.getHeader(context, "Error while retrieving related items", "Handle: " + log.error(LogHelper.getHeader(context, "Error while retrieving related items", "Handle: " + item.getHandle()), e); } return results; 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 8c5f92bd97..00b70f93d5 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java @@ -19,7 +19,7 @@ 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.LogManager; +import org.dspace.core.LogHelper; import org.dspace.discovery.indexobject.IndexableCollection; import org.springframework.beans.factory.annotation.Autowired; @@ -67,7 +67,7 @@ public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIn context.uncacheEntity(resourcePolicy); } } catch (SQLException e) { - log.error(LogManager.getHeader(context, "Error while indexing resource policies", + log.error(LogHelper.getHeader(context, "Error while indexing resource policies", "Collection: (id " + col.getID() + " type " + col.getName() + ")" )); } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServicePrivateItemPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServicePrivateItemPlugin.java index 87302390e1..db543141e1 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServicePrivateItemPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServicePrivateItemPlugin.java @@ -16,7 +16,7 @@ import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrQuery; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.springframework.beans.factory.annotation.Autowired; /** @@ -50,7 +50,7 @@ public class SolrServicePrivateItemPlugin implements SolrServiceSearchPlugin { } } catch (SQLException ex) { - log.error(LogManager.getHeader(context, "Error looking up authorization rights of current user", + log.error(LogHelper.getHeader(context, "Error looking up authorization rights of current user", ""), ex); } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java index e5eb0d9019..d19616a85e 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceResourceRestrictionPlugin.java @@ -28,7 +28,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.discovery.indexobject.IndexableClaimedTask; import org.dspace.discovery.indexobject.IndexableDSpaceObject; import org.dspace.discovery.indexobject.IndexableInProgressSubmission; @@ -129,7 +129,7 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu dso = ContentServiceFactory.getInstance().getDSpaceObjectService(dso).getParentObject(context, dso); } } catch (SQLException e) { - log.error(LogManager.getHeader(context, "Error while indexing resource policies", + log.error(LogHelper.getHeader(context, "Error while indexing resource policies", "DSpace object: (id " + dso.getID() + " type " + dso.getType() + ")" )); } @@ -175,7 +175,7 @@ public class SolrServiceResourceRestrictionPlugin implements SolrServiceIndexPlu solrQuery.addFilterQuery(resourceQuery.toString()); } } catch (SQLException e) { - log.error(LogManager.getHeader(context, "Error while adding resource policy information to query", ""), e); + log.error(LogHelper.getHeader(context, "Error while adding resource policy information to query", ""), e); } } } 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 07948bb0c3..17c9fa429e 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 @@ -41,7 +41,7 @@ import org.dspace.content.authority.service.MetadataAuthorityService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.discovery.FullTextContentStreams; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchUtils; @@ -618,7 +618,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl impleme // Create a table row EPerson e = ePersonDAO.create(context, new EPerson()); - log.info(LogManager.getHeader(context, "create_eperson", "eperson_id=" + log.info(LogHelper.getHeader(context, "create_eperson", "eperson_id=" + e.getID())); context.addEvent(new Event(Event.CREATE, Constants.EPERSON, e.getID(), @@ -385,7 +385,7 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme // Remove ourself ePersonDAO.delete(context, ePerson); - log.info(LogManager.getHeader(context, "delete_eperson", + log.info(LogHelper.getHeader(context, "delete_eperson", "eperson_id=" + ePerson.getID())); } @@ -486,7 +486,7 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl impleme ePersonDAO.save(context, ePerson); - log.info(LogManager.getHeader(context, "update_eperson", + log.info(LogHelper.getHeader(context, "update_eperson", "eperson_id=" + ePerson.getID())); if (ePerson.isModified()) { 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 35c0cc7963..4c98ed20c8 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -34,7 +34,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.dao.Group2GroupCacheDAO; import org.dspace.eperson.dao.GroupDAO; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -110,7 +110,7 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements // Create a table row Group g = groupDAO.create(context, new Group()); - log.info(LogManager.getHeader(context, "create_group", "group_id=" + log.info(LogHelper.getHeader(context, "create_group", "group_id=" + g.getID())); context.addEvent(new Event(Event.CREATE, Constants.GROUP, g.getID(), null, getIdentifiers(context, g))); @@ -502,7 +502,7 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements groupDAO.delete(context, group); rethinkGroupCache(context, false); - log.info(LogManager.getHeader(context, "delete_group", "group_id=" + log.info(LogHelper.getHeader(context, "delete_group", "group_id=" + group.getID())); } @@ -595,7 +595,7 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements group.clearGroupsChanged(); } - log.info(LogManager.getHeader(context, "update_group", "group_id=" + log.info(LogHelper.getHeader(context, "update_group", "group_id=" + group.getID())); } 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 9a5d699965..9e5ecaa4fb 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java @@ -37,7 +37,7 @@ 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.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.SubscribeService; import org.dspace.handle.factory.HandleServiceFactory; @@ -262,8 +262,8 @@ public class SubscribeCLITool { if (emailText.length() > 0) { if (test) { - log.info(LogManager.getHeader(context, "subscription:", "eperson=" + eperson.getEmail())); - log.info(LogManager.getHeader(context, "subscription:", "text=" + emailText.toString())); + log.info(LogHelper.getHeader(context, "subscription:", "eperson=" + eperson.getEmail())); + log.info(LogHelper.getHeader(context, "subscription:", "text=" + emailText.toString())); } else { @@ -272,7 +272,7 @@ public class SubscribeCLITool { email.addArgument(emailText.toString()); email.send(); - log.info(LogManager.getHeader(context, "sent_subscription", "eperson_id=" + eperson.getID())); + log.info(LogHelper.getHeader(context, "sent_subscription", "eperson_id=" + eperson.getID())); } 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 34dbcdbeaa..81c367f0ea 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,7 @@ import org.dspace.content.Collection; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.dao.SubscriptionDAO; import org.dspace.eperson.service.SubscribeService; import org.springframework.beans.factory.annotation.Autowired; @@ -82,7 +82,7 @@ public class SubscribeServiceImpl implements SubscribeService { } else { subscriptionDAO.deleteByCollectionAndEPerson(context, collection, eperson); - log.info(LogManager.getHeader(context, "unsubscribe", + log.info(LogHelper.getHeader(context, "unsubscribe", "eperson_id=" + eperson.getID() + ",collection_id=" + collection.getID())); } diff --git a/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java b/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java index 290345aff1..227a31e6fb 100644 --- a/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java @@ -21,7 +21,7 @@ import org.dspace.content.dto.MetadataValueDTO; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.external.model.ExternalDataObject; import org.dspace.external.provider.ExternalDataProvider; import org.dspace.external.service.ExternalDataService; @@ -105,7 +105,7 @@ public class ExternalDataServiceImpl implements ExternalDataService { metadataValueDTO.getConfidence()); } - log.info(LogManager.getHeader(context, "create_item_from_externalDataObject", "Created item" + + log.info(LogHelper.getHeader(context, "create_item_from_externalDataObject", "Created item" + "with id: " + item.getID() + " from source: " + externalDataObject.getSource() + " with identifier: " + externalDataObject.getId())); return workspaceItem; diff --git a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java index 7b55a2dd8c..a4d1e2a0f4 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java @@ -22,7 +22,7 @@ import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -76,8 +76,7 @@ public class HandleIdentifierProvider extends IdentifierProvider { return id; } catch (IOException | SQLException | AuthorizeException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID(), e); } } @@ -91,8 +90,7 @@ public class HandleIdentifierProvider extends IdentifierProvider { populateHandleMetadata(context, item, identifier); } } catch (IOException | IllegalStateException | SQLException | AuthorizeException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID(), e); } } @@ -103,8 +101,7 @@ public class HandleIdentifierProvider extends IdentifierProvider { try { handleService.createHandle(context, dso, identifier); } catch (IllegalStateException | SQLException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -126,8 +123,7 @@ public class HandleIdentifierProvider extends IdentifierProvider { try { return handleService.createHandle(context, dso); } catch (SQLException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -139,7 +135,7 @@ public class HandleIdentifierProvider extends IdentifierProvider { identifier = handleService.parseHandle(identifier); return handleService.resolveToObject(context, identifier); } catch (IllegalStateException | SQLException e) { - log.error(LogManager.getHeader(context, "Error while resolving handle to item", "handle: " + identifier), + log.error(LogHelper.getHeader(context, "Error while resolving handle to item", "handle: " + identifier), e); } // throw new IllegalStateException("Unsupported Handle Type " diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java index 193f747895..abf7a737f8 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java @@ -27,7 +27,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -89,7 +89,7 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { populateHandleMetadata(context, dso, id); } } catch (IOException | SQLException | AuthorizeException e) { - log.error(LogManager.getHeader(context, "Error while attempting to create handle", + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + (dso != null ? dso.getID() : "")), e); throw new RuntimeException( "Error while attempting to create identifier for Item id: " + (dso != null ? dso.getID() : "")); @@ -240,8 +240,7 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { try { handleService.createHandle(context, dso, identifier); } catch (IllegalStateException | SQLException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -274,8 +273,7 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { } return handleId; } catch (SQLException | AuthorizeException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -287,7 +285,7 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { identifier = handleService.parseHandle(identifier); return handleService.resolveToObject(context, identifier); } catch (IllegalStateException | SQLException e) { - log.error(LogManager.getHeader(context, "Error while resolving handle to item", "handle: " + identifier), + log.error(LogHelper.getHeader(context, "Error while resolving handle to item", "handle: " + identifier), e); } return null; diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java index e15abc43b4..f8f81d1620 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -22,7 +22,7 @@ import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -206,8 +206,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident } } } catch (IOException | SQLException | AuthorizeException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID(), e); } } @@ -259,8 +258,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident try { handleService.createHandle(context, dso, identifier); } catch (IllegalStateException | SQLException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -293,8 +291,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident } return handleId; } catch (SQLException | AuthorizeException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -305,7 +302,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident try { return handleService.resolveToObject(context, identifier); } catch (IllegalStateException | SQLException e) { - log.error(LogManager.getHeader(context, "Error while resolving handle to item", "handle: " + identifier), + log.error(LogHelper.getHeader(context, "Error while resolving handle to item", "handle: " + identifier), e); } return null; @@ -353,8 +350,7 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident } } } catch (RuntimeException | SQLException e) { - log.error( - LogManager.getHeader(context, "Error while attempting to register doi", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, "Error while attempting to register doi", "Item id: " + dso.getID()), e); throw new IdentifierException("Error while moving doi identifier", e); } diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index aa193f30bc..8c03a9767d 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -41,7 +41,7 @@ import org.dspace.content.service.BitstreamService; import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.scripts.service.ProcessService; @@ -82,7 +82,7 @@ public class ProcessServiceImpl implements ProcessService { process.setParameters(DSpaceCommandLineParameter.concatenate(parameters)); process.setCreationTime(new Date()); Process createdProcess = processDAO.create(context, process); - log.info(LogManager.getHeader(context, "process_create", + log.info(LogHelper.getHeader(context, "process_create", "Process has been created for eperson with email " + ePerson.getEmail() + " with ID " + createdProcess.getID() + " and scriptName " + scriptName + " and parameters " + parameters)); @@ -124,7 +124,7 @@ public class ProcessServiceImpl implements ProcessService { process.setProcessStatus(ProcessStatus.RUNNING); process.setStartTime(new Date()); update(context, process); - log.info(LogManager.getHeader(context, "process_start", "Process with ID " + process.getID() + log.info(LogHelper.getHeader(context, "process_start", "Process with ID " + process.getID() + " and name " + process.getName() + " has started")); } @@ -134,7 +134,7 @@ public class ProcessServiceImpl implements ProcessService { process.setProcessStatus(ProcessStatus.FAILED); process.setFinishedTime(new Date()); update(context, process); - log.info(LogManager.getHeader(context, "process_fail", "Process with ID " + process.getID() + log.info(LogHelper.getHeader(context, "process_fail", "Process with ID " + process.getID() + " and name " + process.getName() + " has failed")); } @@ -144,7 +144,7 @@ public class ProcessServiceImpl implements ProcessService { process.setProcessStatus(ProcessStatus.COMPLETED); process.setFinishedTime(new Date()); update(context, process); - log.info(LogManager.getHeader(context, "process_complete", "Process with ID " + process.getID() + log.info(LogHelper.getHeader(context, "process_complete", "Process with ID " + process.getID() + " and name " + process.getName() + " has been completed")); } @@ -177,7 +177,7 @@ public class ProcessServiceImpl implements ProcessService { bitstreamService.delete(context, bitstream); } processDAO.delete(context, process); - log.info(LogManager.getHeader(context, "process_delete", "Process with ID " + process.getID() + log.info(LogHelper.getHeader(context, "process_delete", "Process with ID " + process.getID() + " and name " + process.getName() + " has been deleted")); } diff --git a/dspace-api/src/main/java/org/dspace/statistics/AnonymizeStatistics.java b/dspace-api/src/main/java/org/dspace/statistics/AnonymizeStatistics.java index 279b2f4215..ef2a612133 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/AnonymizeStatistics.java +++ b/dspace-api/src/main/java/org/dspace/statistics/AnonymizeStatistics.java @@ -16,7 +16,7 @@ import static java.util.Collections.singletonList; import static org.apache.commons.cli.Option.builder; import static org.apache.commons.lang.time.DateFormatUtils.format; import static org.apache.logging.log4j.LogManager.getLogger; -import static org.dspace.core.LogManager.getHeader; +import static org.dspace.core.LogHelper.getHeader; import static org.dspace.statistics.SolrLoggerServiceImpl.DATE_FORMAT_8601; import java.io.IOException; diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/IrusExportUsageEventListener.java b/dspace-api/src/main/java/org/dspace/statistics/export/IrusExportUsageEventListener.java index 5620dace08..807184a4bb 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/IrusExportUsageEventListener.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/IrusExportUsageEventListener.java @@ -13,7 +13,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.services.ConfigurationService; import org.dspace.services.model.Event; import org.dspace.statistics.export.processor.BitstreamEventProcessor; @@ -66,7 +66,7 @@ public class IrusExportUsageEventListener extends AbstractUsageEventListener { } catch (Exception e1) { type = -1; } - log.error(LogManager.getHeader(ue.getContext(), "Error while processing export of use event", + log.error(LogHelper.getHeader(ue.getContext(), "Error while processing export of use event", "Id: " + id + " type: " + type), e); } } diff --git a/dspace-api/src/main/java/org/dspace/usage/LoggerUsageEventListener.java b/dspace-api/src/main/java/org/dspace/usage/LoggerUsageEventListener.java index aa24db0775..246239abbb 100644 --- a/dspace-api/src/main/java/org/dspace/usage/LoggerUsageEventListener.java +++ b/dspace-api/src/main/java/org/dspace/usage/LoggerUsageEventListener.java @@ -11,7 +11,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Constants; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.services.model.Event; import org.dspace.usage.UsageEvent.Action; @@ -33,7 +33,7 @@ public class LoggerUsageEventListener extends AbstractUsageEventListener { if (event instanceof UsageEvent && !(event instanceof UsageSearchEvent)) { UsageEvent ue = (UsageEvent) event; - log.info(LogManager.getHeader( + log.info(LogHelper.getHeader( ue.getContext(), formatAction(ue.getAction(), ue.getObject()), formatMessage(ue.getObject())) 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 d77eb16ea7..5525e42fcc 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -45,7 +45,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Email; import org.dspace.core.I18nUtil; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.curate.service.XmlWorkflowCuratorService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -317,7 +317,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // current step cannot be completed and we must exit immediately. if (!xmlWorkflowCuratorService.doCuration(context, wfi)) { // don't proceed - either curation tasks queued, or item rejected - log.info(LogManager.getHeader(context, "start_workflow", + log.info(LogHelper.getHeader(context, "start_workflow", "workflow_item_id=" + wfi.getID() + ",item_id=" + wfi.getItem().getID() + ",collection_id=" + wfi.getCollection().getID() @@ -328,7 +328,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // Activate the step. firstActionConfig.getProcessingAction().activate(context, wfi); - log.info(LogManager.getHeader(context, "start_workflow", + log.info(LogHelper.getHeader(context, "start_workflow", firstActionConfig.getProcessingAction() + " workflow_item_id=" + wfi.getID() + "item_id=" + wfi.getItem().getID() @@ -366,7 +366,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // current step cannot be completed and we must exit immediately. if (!xmlWorkflowCuratorService.doCuration(c, wi)) { // don't proceed - either curation tasks queued, or item rejected - log.info(LogManager.getHeader(c, "advance_workflow", + log.info(LogHelper.getHeader(c, "advance_workflow", "workflow_item_id=" + wi.getID() + ",item_id=" + wi.getItem().getID() + ",collection_id=" + wi.getCollection().getID() @@ -391,7 +391,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { throw new AuthorizeException("You are not allowed to to perform this task."); } } catch (WorkflowConfigurationException e) { - log.error(LogManager.getHeader(c, "error while executing state", + log.error(LogHelper.getHeader(c, "error while executing state", "workflow: " + workflow.getID() + " action: " + currentActionConfig.getId() + " workflowItemId: " + workflowItemId), e); @@ -509,7 +509,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { } - log.error(LogManager.getHeader(c, "Invalid step outcome", "Workflow item id: " + wfi.getID())); + log.error(LogHelper.getHeader(c, "Invalid step outcome", "Workflow item id: " + wfi.getID())); throw new WorkflowException("Invalid step outcome"); } @@ -558,7 +558,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { DSpaceServicesFactory.getInstance().getEventService().fireEvent(usageWorkflowEvent); } catch (SQLException e) { //Catch all errors we do not want our workflow to crash because the logging threw an exception - log.error(LogManager.getHeader(c, "Error while logging workflow event", "Workflow Item: " + wfi.getID()), + log.error(LogHelper.getHeader(c, "Error while logging workflow event", "Workflow Item: " + wfi.getID()), e); } } @@ -615,7 +615,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // Remove (if any) the workflowItemroles for this item workflowItemRoleService.deleteForWorkflowItem(context, wfi); - log.info(LogManager.getHeader(context, "archive_item", "workflow_item_id=" + log.info(LogHelper.getHeader(context, "archive_item", "workflow_item_id=" + wfi.getID() + "item_id=" + item.getID() + "collection_id=" + collection.getID())); @@ -630,7 +630,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { itemService.update(context, item); // Log the event - log.info(LogManager.getHeader(context, "install_item", "workflow_item_id=" + log.info(LogHelper.getHeader(context, "install_item", "workflow_item_id=" + wfi.getID() + ", item_id=" + item.getID() + "handle=FIXME")); return item; @@ -680,7 +680,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { email.send(); } } catch (MessagingException e) { - log.warn(LogManager.getHeader(context, "notifyOfArchive", + log.warn(LogHelper.getHeader(context, "notifyOfArchive", "cannot email user" + " item_id=" + item.getID())); } } @@ -713,7 +713,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { email.send(); } } catch (MessagingException e) { - log.warn(LogManager.getHeader(c, "notifyOfCuration", + log.warn(LogHelper.getHeader(c, "notifyOfCuration", "cannot email users of workflow_item_id " + wi.getID() + ": " + e.getMessage())); } @@ -981,7 +981,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { xmlWorkflowItemService.deleteWrapper(context, wi); // Now delete the item itemService.delete(context, myitem); - log.info(LogManager.getHeader(context, "delete_workflow", "workflow_item_id=" + log.info(LogHelper.getHeader(context, "delete_workflow", "workflow_item_id=" + workflowID + "item_id=" + itemID + "collection_id=" + collID + "eperson_id=" + e.getID())); @@ -1039,7 +1039,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { revokeReviewerPolicies(context, myitem); // notify that it's been rejected notifyOfReject(context, wi, e, rejection_message); - log.info(LogManager.getHeader(context, "reject_workflow", "workflow_item_id=" + log.info(LogHelper.getHeader(context, "reject_workflow", "workflow_item_id=" + wi.getID() + "item_id=" + wi.getItem().getID() + "collection_id=" + wi.getCollection().getID() + "eperson_id=" + e.getID())); @@ -1063,7 +1063,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // convert into personal workspace WorkspaceItem wsi = returnToWorkspace(c, wi); - log.info(LogManager.getHeader(c, "abort_workflow", "workflow_item_id=" + log.info(LogHelper.getHeader(c, "abort_workflow", "workflow_item_id=" + wi.getID() + "item_id=" + wsi.getItem().getID() + "collection_id=" + wi.getCollection().getID() + "eperson_id=" + e.getID())); @@ -1114,7 +1114,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { workspaceItemService.update(c, workspaceItem); //myitem.update(); - log.info(LogManager.getHeader(c, "return_to_workspace", + log.info(LogHelper.getHeader(c, "return_to_workspace", "workflow_item_id=" + wfi.getID() + "workspace_item_id=" + workspaceItem.getID())); @@ -1192,7 +1192,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { } } catch (IOException | MessagingException ex) { // log this email error - log.warn(LogManager.getHeader(c, "notify_of_reject", + log.warn(LogHelper.getHeader(c, "notify_of_reject", "cannot email user" + " eperson_id" + e.getID() + " eperson_email" + e.getEmail() + " workflow_item_id" + wi.getID())); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/migration/RestartWorkflow.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/migration/RestartWorkflow.java index 60e520eee0..8490107518 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/migration/RestartWorkflow.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/migration/RestartWorkflow.java @@ -19,7 +19,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; @@ -137,7 +137,7 @@ public class RestartWorkflow { WorkspaceItem wsi = workflowService .sendWorkflowItemBackSubmission(context, workflowItem, myEPerson, provenance, ""); - log.info(LogManager.getHeader(context, "restart_workflow", "workflow_item_id=" + log.info(LogHelper.getHeader(context, "restart_workflow", "workflow_item_id=" + workflowItem.getID() + "item_id=" + workflowItem.getItem().getID() + "collection_id=" + workflowItem.getCollection().getID())); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java index 3c8d85997a..5d934ba189 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AssignOriginalSubmitterAction.java @@ -17,7 +17,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.RoleMembers; @@ -87,7 +87,7 @@ public class AssignOriginalSubmitterAction extends UserSelectionAction { xmlWorkflowService.getMyDSpaceLink() ); } catch (MessagingException e) { - log.info(LogManager.getHeader(c, "error emailing user(s) for claimed task", + log.info(LogHelper.getHeader(c, "error emailing user(s) for claimed task", "step: " + getParent().getStep().getId() + " workflowitem: " + wfi.getID())); } } 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 3f87c26029..51f4bf0a93 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 @@ -16,7 +16,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.GroupService; import org.dspace.xmlworkflow.Role; @@ -82,22 +82,22 @@ public class AutoAssignAction extends UserSelectionAction { workflowItemRoleService.delete(c, workflowItemRole); } } else { - log.warn(LogManager.getHeader(c, "Error while executing auto assign action", + log.warn(LogHelper.getHeader(c, "Error while executing auto assign action", "No valid next action. Workflow item:" + wfi.getID())); } } } catch (SQLException e) { - log.error(LogManager.getHeader(c, "Error while executing auto assign action", + log.error(LogHelper.getHeader(c, "Error while executing auto assign action", "Workflow item: " + wfi.getID() + " step :" + getParent().getStep().getId()), e); throw e; } catch (AuthorizeException e) { - log.error(LogManager.getHeader(c, "Error while executing auto assign action", + log.error(LogHelper.getHeader(c, "Error while executing auto assign action", "Workflow item: " + wfi.getID() + " step :" + getParent().getStep().getId()), e); throw e; } catch (IOException e) { - log.error(LogManager.getHeader(c, "Error while executing auto assign action", + log.error(LogHelper.getHeader(c, "Error while executing auto assign action", "Workflow item: " + wfi.getID() + " step :" + getParent().getStep().getId()), e); throw e; 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 744bf69136..c9c61908aa 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 @@ -16,7 +16,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -54,7 +54,7 @@ public class ClaimAction extends UserSelectionAction { .createPoolTasks(context, wfItem, allroleMembers, owningStep, getParent()); alertUsersOnActivation(context, wfItem, allroleMembers); } else { - log.info(LogManager.getHeader(context, "warning while activating claim action", + log.info(LogHelper.getHeader(context, "warning while activating claim action", "No group or person was found for the following roleid: " + getParent() .getStep().getRole().getId())); } @@ -96,7 +96,7 @@ public class ClaimAction extends UserSelectionAction { xmlWorkflowService.getMyDSpaceLink() ); } catch (MessagingException e) { - log.info(LogManager.getHeader(c, "error emailing user(s) for claimed task", + log.info(LogHelper.getHeader(c, "error emailing user(s) for claimed task", "step: " + getParent().getStep().getId() + " workflowitem: " + wfi.getID())); } } @@ -113,7 +113,7 @@ public class ClaimAction extends UserSelectionAction { } } else { - log.info(LogManager.getHeader(c, "warning while activating claim action", + log.info(LogHelper.getHeader(c, "warning while activating claim action", "No group or person was found for the following roleid: " + getParent() .getStep().getId())); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItemServiceImpl.java index 010c310ba6..181bb9985f 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/XmlWorkflowItemServiceImpl.java @@ -18,7 +18,7 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.xmlworkflow.service.WorkflowRequirementsService; import org.dspace.xmlworkflow.storedcomponents.dao.XmlWorkflowItemDAO; @@ -77,12 +77,12 @@ public class XmlWorkflowItemServiceImpl implements XmlWorkflowItemService { if (workflowItem == null) { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_workflow_item", + log.debug(LogHelper.getHeader(context, "find_workflow_item", "not_found,workflowitem_id=" + id)); } } else { if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "find_workflow_item", + log.debug(LogHelper.getHeader(context, "find_workflow_item", "workflowitem_id=" + id)); } } @@ -176,7 +176,7 @@ public class XmlWorkflowItemServiceImpl implements XmlWorkflowItemService { @Override public void update(Context context, XmlWorkflowItem workflowItem) throws SQLException, AuthorizeException { // FIXME check auth - log.info(LogManager.getHeader(context, "update_workflow_item", + log.info(LogHelper.getHeader(context, "update_workflow_item", "workflowitem_id=" + workflowItem.getID())); // Update the item 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 3436b91013..500176a348 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 @@ -32,7 +32,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.Utils; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; @@ -125,8 +125,7 @@ public class OpenSearchController { qResults = SearchUtils.getSearchService().search(context, container, queryArgs); } catch (SearchServiceException e) { - log.error( - LogManager.getHeader(context, "opensearch", "query=" + log.error(LogHelper.getHeader(context, "opensearch", "query=" + queryArgs.getQuery() + ",error=" + e.getMessage()), e); throw new RuntimeException(e.getMessage(), e); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java index c673db3f51..040e9f9c9c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java @@ -23,7 +23,7 @@ import org.dspace.authenticate.AuthenticationMethod; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; import org.slf4j.Logger; @@ -90,19 +90,19 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider int implicitStatus = authenticationService.authenticateImplicit(newContext, null, null, null, request); if (implicitStatus == AuthenticationMethod.SUCCESS) { - log.info(LogManager.getHeader(newContext, "login", "type=implicit")); + log.info(LogHelper.getHeader(newContext, "login", "type=implicit")); output = createAuthentication(password, newContext); } else { int authenticateResult = authenticationService .authenticate(newContext, name, password, null, request); if (AuthenticationMethod.SUCCESS == authenticateResult) { - log.info(LogManager + log.info(LogHelper .getHeader(newContext, "login", "type=explicit")); output = createAuthentication(password, newContext); } else { - log.info(LogManager.getHeader(newContext, "failed_login", "email=" + log.info(LogHelper.getHeader(newContext, "failed_login", "email=" + name + ", result=" + authenticateResult)); throw new BadCredentialsException("Login failed"); @@ -132,8 +132,7 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider return new DSpaceAuthentication(ePerson, getGrantedAuthorities(context)); } else { - log.info( - LogManager.getHeader(context, "failed_login", "No eperson with an non-blank e-mail address found")); + log.info(LogHelper.getHeader(context, "failed_login", "No eperson with an non-blank e-mail address found")); throw new BadCredentialsException("Login failed"); } } 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 c666d9d01d..add7cb45ed 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 @@ -24,7 +24,7 @@ import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.InvalidSearchRequestException; import org.dspace.app.rest.parameter.SearchFilter; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.discovery.DiscoverFacetField; import org.dspace.discovery.DiscoverFilterQuery; import org.dspace.discovery.DiscoverHitHighlightingField; @@ -230,7 +230,7 @@ public class DiscoverQueryBuilder implements InitializingBean { queryArgs.addYearRangeFacet(facet, facetYearRange); } catch (Exception e) { - log.error(LogManager.getHeader(context, "Error in Discovery while setting up date facet range", + log.error(LogHelper.getHeader(context, "Error in Discovery while setting up date facet range", "date facet: " + facet), e); } diff --git a/dspace-sword/src/main/java/org/dspace/sword/DSpaceSWORDServer.java b/dspace-sword/src/main/java/org/dspace/sword/DSpaceSWORDServer.java index ee6b430278..1a761b1293 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/DSpaceSWORDServer.java +++ b/dspace-sword/src/main/java/org/dspace/sword/DSpaceSWORDServer.java @@ -9,7 +9,7 @@ package org.dspace.sword; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.purl.sword.base.AtomDocumentRequest; import org.purl.sword.base.AtomDocumentResponse; import org.purl.sword.base.Deposit; @@ -56,12 +56,12 @@ public class DSpaceSWORDServer implements SWORDServer { Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager + log.debug(LogHelper .getHeader(context, "sword_do_service_document", "")); } // log the request - log.info(LogManager + log.info(LogHelper .getHeader(context, "sword_service_document_request", "username=" + request.getUsername() + ",on_behalf_of=" + @@ -105,12 +105,11 @@ public class DSpaceSWORDServer implements SWORDServer { Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug( - LogManager.getHeader(context, "sword_do_deposit", "")); + log.debug(LogHelper.getHeader(context, "sword_do_deposit", "")); } // log the request - log.info(LogManager.getHeader(context, "sword_deposit_request", + log.info(LogHelper.getHeader(context, "sword_deposit_request", "username=" + deposit.getUsername() + ",on_behalf_of=" + deposit.getOnBehalfOf())); @@ -155,12 +154,12 @@ public class DSpaceSWORDServer implements SWORDServer { Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager + log.debug(LogHelper .getHeader(context, "sword_do_atom_document", "")); } // log the request - log.info(LogManager + log.info(LogHelper .getHeader(context, "sword_atom_document_request", "username=" + adr.getUsername())); diff --git a/dspace-sword/src/main/java/org/dspace/sword/DepositManager.java b/dspace-sword/src/main/java/org/dspace/sword/DepositManager.java index 4491f876cc..ff5ff008d0 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/DepositManager.java +++ b/dspace-sword/src/main/java/org/dspace/sword/DepositManager.java @@ -28,7 +28,7 @@ import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.Utils; import org.purl.sword.base.Deposit; import org.purl.sword.base.DepositResponse; @@ -125,7 +125,7 @@ public class DepositManager { if (swordContext.getOnBehalfOf() != null) { oboEmail = swordContext.getOnBehalfOf().getEmail(); } - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "deposit_failed_authorisation", "user=" + swordContext.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail)); diff --git a/dspace-sword/src/main/java/org/dspace/sword/SWORDAuthenticator.java b/dspace-sword/src/main/java/org/dspace/sword/SWORDAuthenticator.java index ad2a7721a3..e95be15289 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/SWORDAuthenticator.java +++ b/dspace-sword/src/main/java/org/dspace/sword/SWORDAuthenticator.java @@ -29,7 +29,7 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -277,7 +277,7 @@ public class SWORDAuthenticator { "Mediated deposit to this service is not permitted"); } - log.info(LogManager.getHeader(context, "sword_authenticate", + log.info(LogHelper.getHeader(context, "sword_authenticate", "username=" + un + ",on_behalf_of=" + obo)); try { @@ -341,14 +341,14 @@ public class SWORDAuthenticator { if (!authenticated) { // decide what kind of error to throw if (ep != null) { - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "sword_unable_to_set_user", "username=" + un)); throw new SWORDAuthenticationException( "Unable to authenticate the supplied used"); } else { // FIXME: this shouldn't ever happen now, but may as well leave it in just in case // there's a bug elsewhere - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "sword_unable_to_set_on_behalf_of", "username=" + un + ",on_behalf_of=" + obo)); throw new SWORDAuthenticationException( diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/CollectionDepositManagerDSpace.java b/dspace-swordv2/src/main/java/org/dspace/sword2/CollectionDepositManagerDSpace.java index e3e3116dcc..7f77c00f0d 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/CollectionDepositManagerDSpace.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/CollectionDepositManagerDSpace.java @@ -15,7 +15,7 @@ import org.dspace.content.Collection; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.swordapp.server.AuthCredentials; import org.swordapp.server.CollectionDepositManager; import org.swordapp.server.Deposit; @@ -57,8 +57,7 @@ public class CollectionDepositManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug( - LogManager.getHeader(context, "sword_create_new", "")); + log.debug(LogHelper.getHeader(context, "sword_create_new", "")); } // get the deposit target @@ -81,7 +80,7 @@ public class CollectionDepositManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager + log.info(LogHelper .getHeader(context, "deposit_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java b/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java index 9bb6eb8790..454afd80dc 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/ContainerManagerDSpace.java @@ -24,7 +24,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowItemService; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -141,7 +141,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "sword_replace", "")); + log.debug(LogHelper.getHeader(context, "sword_replace", "")); } // get the deposit target @@ -164,7 +164,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager.getHeader( + log.info(LogHelper.getHeader( context, "replace_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail)); @@ -246,7 +246,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader( + log.debug(LogHelper.getHeader( context, "sword_create_new", "")); } @@ -269,7 +269,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "deposit_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail)); @@ -356,7 +356,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "sword_replace", "")); + log.debug(LogHelper.getHeader(context, "sword_replace", "")); } // get the deposit target @@ -379,7 +379,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager.getHeader( + log.info(LogHelper.getHeader( context, "replace_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail)); @@ -463,7 +463,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "sword_delete", "")); + log.debug(LogHelper.getHeader(context, "sword_delete", "")); } // get the deposit target @@ -486,7 +486,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "replace_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail)); @@ -542,7 +542,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader( + log.debug(LogHelper.getHeader( context, "sword_modify_by_headers", "")); } @@ -566,7 +566,7 @@ public class ContainerManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "modify_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail)); diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/DSpaceSwordAPI.java b/dspace-swordv2/src/main/java/org/dspace/sword2/DSpaceSwordAPI.java index b92e246afe..ce8fbc20ff 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/DSpaceSwordAPI.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/DSpaceSwordAPI.java @@ -40,7 +40,7 @@ import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.Utils; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -97,7 +97,7 @@ public class DSpaceSwordAPI { String obo = authCredentials.getOnBehalfOf() != null ? authCredentials.getOnBehalfOf() : "NONE"; - log.info(LogManager.getHeader(sc.getContext(), "sword_auth_request", + log.info(LogHelper.getHeader(sc.getContext(), "sword_auth_request", "username=" + un + ",on_behalf_of=" + obo)); return sc; diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/MediaResourceManagerDSpace.java b/dspace-swordv2/src/main/java/org/dspace/sword2/MediaResourceManagerDSpace.java index 3364cd7350..bbb2220c8e 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/MediaResourceManagerDSpace.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/MediaResourceManagerDSpace.java @@ -27,7 +27,7 @@ import org.dspace.content.Bundle; import org.dspace.content.Item; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.swordapp.server.AuthCredentials; import org.swordapp.server.Deposit; import org.swordapp.server.DepositReceipt; @@ -267,7 +267,7 @@ public class MediaResourceManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "sword_replace", "")); + log.debug(LogHelper.getHeader(context, "sword_replace", "")); } DepositReceipt receipt; @@ -348,7 +348,7 @@ public class MediaResourceManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager + log.info(LogHelper .getHeader(context, "replace_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + @@ -435,7 +435,7 @@ public class MediaResourceManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "sword_delete", "")); + log.debug(LogHelper.getHeader(context, "sword_delete", "")); } SwordUrlManager urlManager = config.getUrlManager(context, config); @@ -492,7 +492,7 @@ public class MediaResourceManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager + log.info(LogHelper .getHeader(context, "replace_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + @@ -565,7 +565,7 @@ public class MediaResourceManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager.getHeader(context, "sword_add", "")); + log.debug(LogHelper.getHeader(context, "sword_add", "")); } // get the deposit target @@ -588,7 +588,7 @@ public class MediaResourceManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager + log.info(LogHelper .getHeader(context, "replace_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + @@ -872,7 +872,7 @@ public class MediaResourceManagerDSpace extends DSpaceSwordAPI if (sc.getOnBehalfOf() != null) { oboEmail = sc.getOnBehalfOf().getEmail(); } - log.info(LogManager + log.info(LogHelper .getHeader(context, "replace_failed_authorisation", "user=" + sc.getAuthenticated().getEmail() + diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/ServiceDocumentManagerDSpace.java b/dspace-swordv2/src/main/java/org/dspace/sword2/ServiceDocumentManagerDSpace.java index 76acc94e1e..57a6585059 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/ServiceDocumentManagerDSpace.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/ServiceDocumentManagerDSpace.java @@ -16,7 +16,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CommunityService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.swordapp.server.AuthCredentials; @@ -59,7 +59,7 @@ public class ServiceDocumentManagerDSpace implements ServiceDocumentManager { WorkflowManagerFactory.getInstance().retrieveServiceDoc(context); if (log.isDebugEnabled()) { - log.debug(LogManager + log.debug(LogHelper .getHeader(context, "sword_do_service_document", "")); } @@ -70,7 +70,7 @@ public class ServiceDocumentManagerDSpace implements ServiceDocumentManager { String obo = authCredentials.getOnBehalfOf() != null ? authCredentials.getOnBehalfOf() : "NONE"; - log.info(LogManager + log.info(LogHelper .getHeader(context, "sword_service_document_request", "username=" + un + ",on_behalf_of=" + obo)); diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/StatementManagerDSpace.java b/dspace-swordv2/src/main/java/org/dspace/sword2/StatementManagerDSpace.java index a12f373b35..3c1de6215f 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/StatementManagerDSpace.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/StatementManagerDSpace.java @@ -21,7 +21,7 @@ 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.LogManager; +import org.dspace.core.LogHelper; import org.swordapp.server.AuthCredentials; import org.swordapp.server.Statement; import org.swordapp.server.StatementManager; @@ -49,7 +49,7 @@ public class StatementManagerDSpace extends DSpaceSwordAPI Context context = sc.getContext(); if (log.isDebugEnabled()) { - log.debug(LogManager + log.debug(LogHelper .getHeader(context, "sword_get_statement", "")); } @@ -60,7 +60,7 @@ public class StatementManagerDSpace extends DSpaceSwordAPI String obo = authCredentials.getOnBehalfOf() != null ? authCredentials.getOnBehalfOf() : "NONE"; - log.info(LogManager.getHeader(context, "sword_get_statement", + log.info(LogHelper.getHeader(context, "sword_get_statement", "username=" + un + ",on_behalf_of=" + obo)); // first thing is to figure out what we're being asked to work on diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordAuthenticator.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordAuthenticator.java index b054a41e3d..54b769388c 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordAuthenticator.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordAuthenticator.java @@ -30,7 +30,7 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -168,7 +168,7 @@ public class SwordAuthenticator { "Mediated deposit to this service is not permitted"); } - log.info(LogManager.getHeader(context, "sword_authenticate", + log.info(LogHelper.getHeader(context, "sword_authenticate", "username=" + un + ",on_behalf_of=" + obo)); try { @@ -233,14 +233,14 @@ public class SwordAuthenticator { if (!authenticated) { // decide what kind of error to throw if (ep != null) { - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "sword_unable_to_set_user", "username=" + un)); throw new SwordAuthException( "Unable to authenticate with the supplied credentials"); } else { // FIXME: this shouldn't ever happen now, but may as well leave it in just in case // there's a bug elsewhere - log.info(LogManager.getHeader(context, + log.info(LogHelper.getHeader(context, "sword_unable_to_set_on_behalf_of", "username=" + un + ",on_behalf_of=" + obo)); throw new SwordAuthException( From f4b1e4b2ff1c7039adfb9242b54035def9f3a102 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 4 Aug 2021 12:02:21 -0400 Subject: [PATCH 0109/1254] [DS-4548] Satisfy Checkstyle. --- .../identifier/HandleIdentifierProvider.java | 16 ++++++++++++---- .../VersionedHandleIdentifierProvider.java | 8 ++++++-- ...leIdentifierProviderWithCanonicalHandles.java | 16 ++++++++++++---- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java index a4d1e2a0f4..1ded40c8f8 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java @@ -76,7 +76,9 @@ public class HandleIdentifierProvider extends IdentifierProvider { return id; } catch (IOException | SQLException | AuthorizeException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID(), e); } } @@ -90,7 +92,9 @@ public class HandleIdentifierProvider extends IdentifierProvider { populateHandleMetadata(context, item, identifier); } } catch (IOException | IllegalStateException | SQLException | AuthorizeException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID(), e); } } @@ -101,7 +105,9 @@ public class HandleIdentifierProvider extends IdentifierProvider { try { handleService.createHandle(context, dso, identifier); } catch (IllegalStateException | SQLException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -123,7 +129,9 @@ public class HandleIdentifierProvider extends IdentifierProvider { try { return handleService.createHandle(context, dso); } catch (SQLException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java index abf7a737f8..b29d47f406 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java @@ -240,7 +240,9 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { try { handleService.createHandle(context, dso, identifier); } catch (IllegalStateException | SQLException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -273,7 +275,9 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { } return handleId; } catch (SQLException | AuthorizeException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java index f8f81d1620..61abbcb580 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -206,7 +206,9 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident } } } catch (IOException | SQLException | AuthorizeException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID(), e); } } @@ -258,7 +260,9 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident try { handleService.createHandle(context, dso, identifier); } catch (IllegalStateException | SQLException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -291,7 +295,9 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident } return handleId; } catch (SQLException | AuthorizeException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to create handle", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to create handle", + "Item id: " + dso.getID()), e); throw new RuntimeException("Error while attempting to create identifier for Item id: " + dso.getID()); } } @@ -350,7 +356,9 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident } } } catch (RuntimeException | SQLException e) { - log.error(LogHelper.getHeader(context, "Error while attempting to register doi", "Item id: " + dso.getID()), e); + log.error(LogHelper.getHeader(context, + "Error while attempting to register doi", + "Item id: " + dso.getID()), e); throw new IdentifierException("Error while moving doi identifier", e); } From 7c42e1788351ab52444a5a36da8ec8d90ef52734 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 4 Aug 2021 13:06:05 -0400 Subject: [PATCH 0110/1254] [DS-4548] Fix forgotten disabled dspace-rest subproject. --- .../org/dspace/rest/CollectionsResource.java | 5 +++-- .../DSpaceAuthenticationProvider.java | 19 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java b/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java index 0edabe6ad2..395a0af766 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java @@ -45,7 +45,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.rest.common.Collection; import org.dspace.rest.common.Item; import org.dspace.rest.common.MetadataEntry; @@ -376,7 +376,8 @@ public class CollectionsResource extends Resource { workflowService.start(context, workspaceItem); } catch (Exception e) { log.error( - LogManager.getHeader(context, "Error while starting workflow", "Item id: " + dspaceItem.getID()), + LogHelper.getHeader(context, "Error while starting workflow", + "Item id: " + dspaceItem.getID()), e); throw new ContextException("Error while starting workflow for item(id=" + dspaceItem.getID() + ")", e); } diff --git a/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java b/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java index 5d0bedb442..eac4c40111 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java +++ b/dspace-rest/src/main/java/org/dspace/rest/authentication/DSpaceAuthenticationProvider.java @@ -13,12 +13,13 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authenticate.AuthenticationMethod; import org.dspace.authenticate.factory.AuthenticateServiceFactory; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.utils.DSpace; @@ -40,7 +41,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; */ public class DSpaceAuthenticationProvider implements AuthenticationProvider { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceAuthenticationProvider.class); + private static final Logger log = LogManager.getLogger(); protected AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance() .getAuthenticationService(); @@ -62,7 +63,7 @@ public class DSpaceAuthenticationProvider implements AuthenticationProvider { .authenticateImplicit(context, null, null, null, httpServletRequest); if (implicitStatus == AuthenticationMethod.SUCCESS) { - log.info(LogManager.getHeader(context, "login", "type=implicit")); + log.info(LogHelper.getHeader(context, "login", "type=implicit")); addSpecialGroupsToGrantedAuthorityList(context, httpServletRequest, grantedAuthorities); return createAuthenticationToken(password, context, grantedAuthorities); @@ -72,15 +73,13 @@ public class DSpaceAuthenticationProvider implements AuthenticationProvider { if (AuthenticationMethod.SUCCESS == authenticateResult) { addSpecialGroupsToGrantedAuthorityList(context, httpServletRequest, grantedAuthorities); - log.info(LogManager - .getHeader(context, "login", "type=explicit")); + log.info(LogHelper.getHeader(context, "login", "type=explicit")); return createAuthenticationToken(password, context, grantedAuthorities); } else { - log.info(LogManager.getHeader(context, "failed_login", "email=" - + name + ", result=" - + authenticateResult)); + log.info(LogHelper.getHeader(context, "failed_login", + "email=" + name + ", result=" + authenticateResult)); throw new BadCredentialsException("Login failed"); } } @@ -117,8 +116,8 @@ public class DSpaceAuthenticationProvider implements AuthenticationProvider { return new UsernamePasswordAuthenticationToken(ePerson.getEmail(), password, grantedAuthorities); } else { - log.info( - LogManager.getHeader(context, "failed_login", "No eperson with an non-blank e-mail address found")); + log.info(LogHelper.getHeader(context, "failed_login", + "No eperson with an non-blank e-mail address found")); throw new BadCredentialsException("Login failed"); } } From 453b0b2cdfd8f2011a4a8cf94395509777c11693 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 4 Aug 2021 14:59:56 -0400 Subject: [PATCH 0111/1254] [DS-4548] Fix test failure: break circular dependency between DSpacePermissionEvaluator and ConverterService. Also fix missing versions in deprecated dspace-rest POM. --- .../java/org/dspace/app/rest/converter/ConverterService.java | 3 --- dspace/modules/rest/pom.xml | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java index 7d51d727cd..caf9a54ffa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java @@ -79,9 +79,6 @@ public class ConverterService { @Autowired private List projections; - @Autowired - private DSpacePermissionEvaluator dSpacePermissionEvaluator; - @Autowired private WebSecurityExpressionEvaluator webSecurityExpressionEvaluator; diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index b1129f90f4..65edaaecad 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -133,11 +133,13 @@ org.dspace dspace-rest + ${project.version} war org.dspace dspace-rest + ${project.version} jar classes From ee8270670811c8796c9382a2866b09d632051b44 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 4 Aug 2021 15:13:51 -0400 Subject: [PATCH 0112/1254] [DS-4548] More Checkstyle fixes. --- .../java/org/dspace/app/rest/converter/ConverterService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java index caf9a54ffa..18fc119eef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java @@ -31,7 +31,6 @@ import org.dspace.app.rest.model.hateoas.HALResource; import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.DSpaceRestRepository; -import org.dspace.app.rest.security.DSpacePermissionEvaluator; import org.dspace.app.rest.security.WebSecurityExpressionEvaluator; import org.dspace.app.rest.utils.Utils; import org.dspace.services.RequestService; From 9d1f526a5af04ef25da5fffa3b7e8520290bd030 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 5 Aug 2021 13:55:21 -0400 Subject: [PATCH 0113/1254] Fix other case of calling Pattern.matcher() twice. #3287 --- .../src/main/java/org/dspace/identifier/DOIServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java index 780c33acf3..99643db33f 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java @@ -108,7 +108,7 @@ public class DOIServiceImpl implements DOIService { } Matcher matcher = DOI_URL_PATTERN.matcher(identifier); - if (DOI_URL_PATTERN.matcher(identifier).matches()) { // various old URL forms + if (matcher.matches()) { // various old URL forms return resolver + matcher.group(DOI_URL_PATTERN_PATH_GROUP); } From f68fb37530d51a878cf83a0711bdd97f072c8de6 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Aug 2021 11:03:45 -0500 Subject: [PATCH 0114/1254] Cleanup of LICENSES_THIRD_PARTY by improving grouping of licenses --- LICENSES_THIRD_PARTY | 227 +++++------------- pom.xml | 29 ++- .../third-party-file-groupByLicense.ftl | 2 +- 3 files changed, 84 insertions(+), 174 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 803920b0f2..21eadef67d 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -15,7 +15,7 @@ PLEASE NOTE: Some dependencies may be listed under multiple licenses if they are dual-licensed. This is especially true of anything listed as "GNU General Public Library" below, as DSpace actually does NOT allow for any dependencies that are solely released under GPL terms. For more info see: -https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines +https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines --------------------------------------------------- (MIT-style) netCDF C library license: @@ -26,28 +26,6 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * netCDF-4 IOSP JNI connection to C library (edu.ucar:netcdf4:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/netcdf4/) * udunits (edu.ucar:udunits:4.5.5 - http://www.unidata.ucar.edu/software/udunits//) - 3-Clause BSD License: - - * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.11.0 - https://developers.google.com/protocol-buffers/protobuf-java/) - - Apache License v2: - - * parso (com.epam:parso:2.0.11 - https://github.com/epam/parso) - - Apache License v2.0: - - * Java Native Access (net.java.dev.jna:jna:5.5.0 - https://github.com/java-native-access/jna) - - Apache License, 2.0: - - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - - Apache License, version 2.0: - - * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.3.2.Final - http://www.jboss.org) - Apache Software License, Version 2.0: * Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net) @@ -56,6 +34,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.10.50 - https://aws.amazon.com/sdkforjava) * jcommander (com.beust:jcommander:1.78 - https://jcommander.org) * HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc) + * parso (com.epam:parso:2.0.11 - https://github.com/epam/parso) * ClassMate (com.fasterxml:classmate:1.3.0 - http://github.com/cowtowncoder/java-classmate) * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.12.3 - http://github.com/FasterXML/jackson) * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.12.3 - https://github.com/FasterXML/jackson-core) @@ -64,6 +43,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.10.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.10.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) + * Woodstox (com.fasterxml.woodstox:woodstox-core:5.0.3 - https://github.com/FasterXML/woodstox) * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.6 - https://github.com/flipkart-incubator/zjsonpatch/) * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.8.4 - https://github.com/ben-manes/caffeine) * Open JSON (com.github.openjson:openjson:1.0.12 - https://github.com/openjson/openjson) @@ -112,6 +92,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * Apache Commons Logging (commons-logging:commons-logging:1.2 - http://commons.apache.org/proper/commons-logging/) * Apache Commons Validator (commons-validator:commons-validator:1.5.0 - http://commons.apache.org/proper/commons-validator/) * Boilerpipe -- Boilerplate Removal and Fulltext Extraction from HTML pages (de.l3s.boilerpipe:boilerpipe:1.1.0 - http://code.google.com/p/boilerpipe/) + * SentimentAnalysisParser (edu.usc.ir:sentiment-analysis-parser:0.1 - https://github.com/USCDataScience/SentimentAnalysisParser) * Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core) * Graphite Integration for Metrics (io.dropwizard.metrics:metrics-graphite:4.1.5 - https://metrics.dropwizard.io/metrics-graphite) * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) @@ -139,6 +120,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.10.20 - https://bytebuddy.net/byte-buddy) * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.10.20 - https://bytebuddy.net/byte-buddy-agent) * eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties) + * Java Native Access (net.java.dev.jna:jna:5.5.0 - https://github.com/java-native-access/jna) * "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/) * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:1.2 - http://www.minidev.net/) * JSON Small and Fast Parser (net.minidev:json-smart:2.3 - http://www.minidev.net/) @@ -297,10 +279,16 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * flyway-core (org.flywaydb:flyway-core:6.5.5 - https://flywaydb.org/flyway-core) * Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava) * Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.0.18.Final - http://hibernate.org/validator/hibernate-validator) * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.0.18.Final - http://hibernate.org/validator/hibernate-validator-cdi) * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) * Java Annotation Indexer (org.jboss:jandex:2.1.1.Final - http://www.jboss.org/jandex) + * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.3.2.Final - http://www.jboss.org) + * JDOM (org.jdom:jdom:1.1.3 - http://www.jdom.org) + * JDOM (org.jdom:jdom2:2.0.6 - http://www.jdom.org) * jtwig-core (org.jtwig:jtwig-core:5.87.0.RELEASE - http://jtwig.org) * jtwig-reflection (org.jtwig:jtwig-reflection:5.87.0.RELEASE - http://jtwig.org) * jtwig-spring (org.jtwig:jtwig-spring:5.87.0.RELEASE - http://jtwig.org) @@ -362,6 +350,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * ISO Parser (org.tallison:isoparser:1.9.41.2 - https://github.com/tballison/mp4parser) * org.tallison:metadata-extractor (org.tallison:metadata-extractor:2.13.0 - https://drewnoakes.com/code/exif/) * XMPCore Shaded (org.tallison.xmp:xmpcore-shaded:6.1.10 - https://github.com/tballison) + * snappy-java (org.xerial.snappy:snappy-java:1.1.7.6 - https://github.com/xerial/snappy-java) * xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.6.3 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.6.4 - https://www.xmlunit.org/) @@ -371,33 +360,14 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * Xerces2-j (xerces:xercesImpl:2.12.0 - https://xerces.apache.org/xerces2-j/) * XML Commons External Components XML APIs (xml-apis:xml-apis:1.4.01 - http://xml.apache.org/commons/components/external/) - Apache-2.0: - - * snappy-java (org.xerial.snappy:snappy-java:1.1.7.6 - https://github.com/xerial/snappy-java) - - BSD 2-Clause: - - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - - BSD 3-clause License w/nuclear disclaimer: - - * Java Advanced Imaging Image I/O Tools API core (standalone) (com.github.jai-imageio:jai-imageio-core:1.4.0 - https://github.com/jai-imageio/jai-imageio-core) - - BSD 3-clause New License: - - * dom4j (org.dom4j:dom4j:2.1.1 - http://dom4j.github.io/) - - BSD Licence 3: - - * Hamcrest (org.hamcrest:hamcrest:2.1 - http://hamcrest.org/JavaHamcrest/) - BSD License: * AntLR Parser Generator (antlr:antlr:2.7.7 - http://www.antlr.org/) * coverity-escapers (com.coverity.security:coverity-escapers:1.1.1 - http://coverity.com/security) + * Java Advanced Imaging Image I/O Tools API core (standalone) (com.github.jai-imageio:jai-imageio-core:1.4.0 - https://github.com/jai-imageio/jai-imageio-core) * JSONLD Java :: Core (com.github.jsonld-java:jsonld-java:0.5.1 - http://github.com/jsonld-java/jsonld-java/jsonld-java/) * curvesapi (com.github.virtuald:curvesapi:1.06 - https://github.com/virtuald/curvesapi) + * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.11.0 - https://developers.google.com/protocol-buffers/protobuf-java/) * dnsjava (dnsjava:dnsjava:2.1.7 - http://www.dnsjava.org) * Units of Measurement API (javax.measure:unit-api:1.0 - http://unitsofmeasurement.github.io/) * jaxen (jaxen:jaxen:1.1.6 - http://jaxen.codehaus.org/) @@ -406,42 +376,36 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * commons-compiler (org.codehaus.janino:commons-compiler:3.0.9 - http://janino-compiler.github.io/commons-compiler/) * janino (org.codehaus.janino:janino:3.0.9 - http://janino-compiler.github.io/janino/) * Stax2 API (org.codehaus.woodstox:stax2-api:3.1.4 - http://wiki.fasterxml.com/WoodstoxStax2) + * dom4j (org.dom4j:dom4j:2.1.1 - http://dom4j.github.io/) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Hamcrest (org.hamcrest:hamcrest:2.1 - http://hamcrest.org/JavaHamcrest/) * Hamcrest All (org.hamcrest:hamcrest-all:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-all) * Hamcrest Core (org.hamcrest:hamcrest-core:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-core) * Hamcrest library (org.hamcrest:hamcrest-library:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-library) * JBibTeX (org.jbibtex:jbibtex:1.0.10 - http://www.jbibtex.org) + * asm (org.ow2.asm:asm:8.0.1 - http://asm.ow2.io/) * asm-analysis (org.ow2.asm:asm-analysis:7.1 - http://asm.ow2.org/) + * 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 - JDBC 4.2 (org.postgresql:postgresql:42.2.9 - https://github.com/pgjdbc/pgjdbc) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) - BSD-2-Clause: + CDDL+GPL License: - * PostgreSQL JDBC Driver - JDBC 4.2 (org.postgresql:postgresql:42.2.9 - https://github.com/pgjdbc/pgjdbc) - - BSD-3-Clause: - - * asm (org.ow2.asm:asm:8.0.1 - http://asm.ow2.io/) - * asm-commons (org.ow2.asm:asm-commons:8.0.1 - http://asm.ow2.io/) - - Bouncy Castle Licence: - - * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk15on:1.65 - http://www.bouncycastle.org/java.html) - * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.65 - http://www.bouncycastle.org/java.html) - * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.65 - http://www.bouncycastle.org/java.html) - - CDDL/GPLv2+CE: - - * JavaBeans Activation Framework (com.sun.activation:javax.activation:1.2.0 - http://java.net/all/javax.activation/) - * JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/) + * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.1 - http://jaxb.java.net/jaxb-runtime-parent/jaxb-runtime) + * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.1 - http://jaxb.java.net/jaxb-txw-parent/txw2) Common Development and Distribution License (CDDL): + * JavaBeans Activation Framework (com.sun.activation:javax.activation:1.2.0 - http://java.net/all/javax.activation/) * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.7 - http://java.net/istack-commons/istack-commons-runtime/) * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) * JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) + * JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/) * javax.annotation API (javax.annotation:javax.annotation-api:1.3.2 - http://jcp.org/en/jsr/detail?id=250) * JavaMail API (compat) (javax.mail:mail:1.4.7 - http://kenai.com/projects/javamail/mail) * Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net) @@ -455,8 +419,6 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.1 - http://jaxb.java.net/jaxb-runtime-parent/jaxb-runtime) - * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.1 - http://jaxb.java.net/jaxb-txw-parent/txw2) * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec) @@ -473,43 +435,26 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * saaj-impl (com.sun.xml.messaging.saaj:saaj-impl:1.4.0-b03 - http://java.net/saaj-impl/) * Extended StAX API (org.jvnet.staxex:stax-ex:1.8 - http://stax-ex.java.net/) - EDL 1.0: + Eclipse Distribution License, Version 1.0: * JavaBeans Activation Framework (com.sun.activation:jakarta.activation:1.2.1 - https://github.com/eclipse-ee4j/jaf/jakarta.activation) * JavaBeans Activation Framework API jar (jakarta.activation:jakarta.activation-api:1.2.1 - https://github.com/eclipse-ee4j/jaf/jakarta.activation-api) * Jakarta Activation API jar (jakarta.activation:jakarta.activation-api:1.2.2 - https://github.com/eclipse-ee4j/jaf/jakarta.activation-api) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - - EPL 2.0: - - * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) - * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) - * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) - * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) - * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) - * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) - * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) - * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - - Eclipse Distribution License (EDL), Version 1.0: - - * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final - http://hibernate.org) - - Eclipse Distribution License - v 1.0: - * jakarta.xml.bind-api (jakarta.xml.bind:jakarta.xml.bind-api:2.3.2 - https://github.com/eclipse-ee4j/jaxb-api/jakarta.xml.bind-api) * Jakarta XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 - https://github.com/eclipse-ee4j/jaxb-api/jakarta.xml.bind-api) - - Eclipse Distribution License v. 1.0: - * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final - http://hibernate.org) Eclipse Public License: + * c3p0 (com.mchange:c3p0:0.9.5.5 - https://github.com/swaldman/c3p0) + * mchange-commons-java (com.mchange:mchange-commons-java:0.2.19 - https://github.com/swaldman/mchange-commons-java) + * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) + * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) + * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) + * JUnit (junit:junit:4.13.1 - http://junit.org) * AspectJ runtime (org.aspectj:aspectjrt:1.8.0 - http://www.aspectj.org) * AspectJ weaver (org.aspectj:aspectjweaver:1.9.5 - http://www.aspectj.org) * Eclipse Compiler for Java(TM) (org.eclipse.jdt:ecj:3.14.0 - http://www.eclipse.org/jdt) @@ -542,49 +487,37 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.34.v20201102 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.34.v20201102 - https://eclipse.org/jetty/http2-parent/http2-server) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) + * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api) + * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) + * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) + * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) + * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) + * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final - http://hibernate.org) * Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) - Eclipse Public License (EPL), Version 1.0: - - * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final - http://hibernate.org) - - Eclipse Public License 1.0: - - * JUnit (junit:junit:4.13.1 - http://junit.org) - - Eclipse Public License v1.0: - - * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec) - - Eclipse Public License, Version 1.0: - - * c3p0 (com.mchange:c3p0:0.9.5.5 - https://github.com/swaldman/c3p0) - * mchange-commons-java (com.mchange:mchange-commons-java:0.2.19 - https://github.com/swaldman/mchange-commons-java) - - GNU General Public License, Version 2 with the Classpath Exception: - - * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec) - GNU Lesser General Public License (LGPL): * SpotBugs Annotations (com.github.spotbugs:spotbugs-annotations:3.1.9 - https://spotbugs.github.io/) * FindBugs-Annotations (com.google.code.findbugs:annotations:3.0.1u2 - http://findbugs.sourceforge.net/) * c3p0 (com.mchange:c3p0:0.9.5.5 - https://github.com/swaldman/c3p0) * mchange-commons-java (com.mchange:mchange-commons-java:0.2.19 - https://github.com/swaldman/mchange-commons-java) + * Java Native Access (net.java.dev.jna:jna:5.5.0 - https://github.com/java-native-access/jna) * JHighlight (org.codelibs:jhighlight:1.0.3 - https://github.com/codelibs/jhighlight) - * im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/) - * JacORB OMG-API (org.jacorb:jacorb-omgapi:3.9 - http://www.jacorb.org) - * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) - * XOM (xom:xom:1.2.5 - http://xom.nu) - - GNU Library General Public License v2.1 or later: - * Hibernate ORM - hibernate-core (org.hibernate:hibernate-core:5.4.10.Final - http://hibernate.org/orm) * Hibernate ORM - hibernate-ehcache (org.hibernate:hibernate-ehcache:5.4.10.Final - http://hibernate.org/orm) * Hibernate ORM - hibernate-jpamodelgen (org.hibernate:hibernate-jpamodelgen:5.4.10.Final - http://hibernate.org/orm) * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:5.1.0.Final - http://hibernate.org) + * im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/) + * JacORB OMG-API (org.jacorb:jacorb-omgapi:3.9 - http://www.jacorb.org) + * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) + * Java RMI API (org.jboss.spec.javax.rmi:jboss-rmi-api_1.0_spec:1.0.6.Final - http://www.jboss.org/jboss-rmi-api_1.0_spec) + * XOM (xom:xom:1.2.5 - http://xom.nu) Go License: @@ -598,21 +531,17 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * jdom (jdom:jdom:1.0 - no url defined) - LGPL, version 2.1: - - * Java Native Access (net.java.dev.jna:jna:5.5.0 - https://github.com/java-native-access/jna) - - MIT: - - * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) - * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.5.1 - https://www.webjars.org) - * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.5.2 - https://www.webjars.org) - MIT License: * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) + * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk15on:1.65 - http://www.bouncycastle.org/java.html) + * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.65 - http://www.bouncycastle.org/java.html) + * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.65 - http://www.bouncycastle.org/java.html) * org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) * Checker Qual (org.checkerframework:checker-qual:3.5.0 - https://checkerframework.org) + * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) + * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Itadaki jbzip2 (org.itadaki:bzip2:0.9.1 - https://code.google.com/p/jbzip2/) * jsoup Java HTML Parser (org.jsoup:jsoup:1.13.1 - https://jsoup.org/) * mockito-core (org.mockito:mockito-core:3.8.0 - https://github.com/mockito/mockito) * mockito-inline (org.mockito:mockito-inline:3.8.0 - https://github.com/mockito/mockito) @@ -620,30 +549,16 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.25 - http://www.slf4j.org) * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.25 - http://www.slf4j.org) * SLF4J API Module (org.slf4j:slf4j-api:1.7.25 - http://www.slf4j.org) - - MIT License (MIT): - - * Itadaki jbzip2 (org.itadaki:bzip2:0.9.1 - https://code.google.com/p/jbzip2/) - - MIT license: - - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - - Modified BSD: - - * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) - * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) + * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.5.1 - https://www.webjars.org) + * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.5.2 - https://www.webjars.org) Mozilla Public License: * juniversalchardet (com.googlecode.juniversalchardet:juniversalchardet:1.0.3 - http://juniversalchardet.googlecode.com/) * h2 (com.h2database:h2:1.4.187 - no url defined) - * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) - - Mozilla Public License Version 2.0: - * Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/) + * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) OGC copyright: @@ -656,20 +571,6 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * XZ for Java (org.tukaani:xz:1.8 - https://tukaani.org/xz/java.html) - Similar to Apache License but with the acknowledgment clause removed: - - * JDOM (org.jdom:jdom:1.1.3 - http://www.jdom.org) - * JDOM (org.jdom:jdom2:2.0.6 - http://www.jdom.org) - - The Apache License, Version 2.0: - - * Woodstox (com.fasterxml.woodstox:woodstox-core:5.0.3 - https://github.com/FasterXML/woodstox) - * SentimentAnalysisParser (edu.usc.ir:sentiment-analysis-parser:0.1 - https://github.com/USCDataScience/SentimentAnalysisParser) - - The GNU General Public License (GPL), Version 2, With Classpath Exception: - - * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) - The JSON License: * JSON in Java (org.json:json:20180130 - https://github.com/douglascrockford/JSON-java) @@ -691,7 +592,3 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) - - lgpl: - - * Java RMI API (org.jboss.spec.javax.rmi:jboss-rmi-api_1.0_spec:1.0.6.Final - http://www.jboss.org/jboss-rmi-api_1.0_spec) diff --git a/pom.xml b/pom.xml index 6be4d348bc..9fbf3f13c2 100644 --- a/pom.xml +++ b/pom.xml @@ -681,33 +681,46 @@ src/main/license/third-party-file-groupByLicense.ftl - Apache Software License, Version 2.0|The Apache Software License, Version 2.0|Apache License Version 2.0|Apache License, Version 2.0|Apache Public License 2.0|Apache License 2.0|Apache Software License - Version 2.0|Apache 2.0 License|Apache 2.0 license|Apache License V2.0|Apache 2|Apache License|Apache|ASF 2.0|Apache 2.0 + Apache Software License, Version 2.0|The Apache Software License, Version 2.0|Apache License Version 2.0|Apache License, Version 2.0|Apache Public License 2.0|Apache License 2.0|Apache Software License - Version 2.0|Apache 2.0 License|Apache 2.0 license|Apache License V2.0|Apache 2|Apache License|Apache|ASF 2.0|Apache 2.0|Apache License v2|Apache License v2.0|Apache License, 2.0|Apache License, version 2.0|Apache-2.0|The Apache License, Version 2.0 Apache Software License, Version 2.0|http://ant-contrib.sourceforge.net/tasks/LICENSE.txt Apache Software License, Version 2.0|The SAX License|The W3C License - BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license + + Apache Software License, Version 2.0|Similar to Apache License but with the acknowledgment clause removed + BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD BSD License|DSpace BSD License|DSpace Sourcecode License BSD License|BSD style modified by Coverity BSD License|http://jaxen.codehaus.org/license.html - Common Development and Distribution License (CDDL)|Common Development and Distribution License (CDDL) v1.0|COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0|Common Development and Distribution License|CDDL, v1.0|CDDL 1.0 license|CDDL 1.0|CDDL 1.1|CDDL + + BSD License|BSD 3-clause License w/nuclear disclaimer + Common Development and Distribution License (CDDL)|Common Development and Distribution License (CDDL) v1.0|COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0|Common Development and Distribution License|CDDL, v1.0|CDDL 1.0 license|CDDL 1.0|CDDL 1.1|CDDL|Dual license consisting of the CDDL v1.1 and GPL v2 Common Development and Distribution License (CDDL)|CDDL + GPLv2 with classpath exception Common Development and Distribution License (CDDL)|CDDL+GPL License - Common Development and Distribution License (CDDL)|GPLv2+CE + Common Development and Distribution License (CDDL)|GPLv2+CE|CDDL/GPLv2+CE Common Development and Distribution License (CDDL)|GPL2 w/ CPE - Eclipse Public License|Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0|EPL 1.0 license + + Common Development and Distribution License (CDDL)|GNU General Public License, Version 2 with the Classpath Exception + Eclipse Distribution License, Version 1.0|Eclipse Distribution License (EDL), Version 1.0|Eclipse Distribution License - v 1.0|Eclipse Distribution License v. 1.0|EDL 1.0 + Eclipse Public License|Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0|EPL 1.0 license|Eclipse Public License (EPL), Version 1.0|Eclipse Public License 1.0|Eclipse Public License v1.0|Eclipse Public License, Version 1.0|EPL 2.0 Eclipse Public License|Common Public License Version 1.0 - GNU Lesser General Public License (LGPL)|The GNU Lesser General Public License, Version 2.1|GNU Lesser General Public License (LGPL), Version 2.1|GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1|GNU Lesser General Public License, version 2.1|GNU LESSER GENERAL PUBLIC LICENSE|GNU Lesser General Public License|GNU Lesser Public License|GNU Lesser General Public License, Version 2.1|Lesser General Public License (LGPL) v 2.1|LGPL 2.1|LGPL 2.1 license|LGPL 3.0 license|LGPL, v2.1 or later|LGPL - MIT License|The MIT License|MIT LICENSE - Mozilla Public License|Mozilla Public License version 1.1|Mozilla Public License 1.1 (MPL 1.1)|MPL 1.1 + + Eclipse Public License|The GNU General Public License (GPL), Version 2, With Classpath Exception + GNU Lesser General Public License (LGPL)|The GNU Lesser General Public License, Version 2.1|GNU Lesser General Public License (LGPL), Version 2.1|GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1|GNU Lesser General Public License, version 2.1|GNU LESSER GENERAL PUBLIC LICENSE|GNU Lesser General Public License|GNU Lesser Public License|GNU Lesser General Public License, Version 2.1|Lesser General Public License (LGPL) v 2.1|LGPL 2.1|LGPL 2.1 license|LGPL 3.0 license|LGPL, v2.1 or later|LGPL|LGPL, version 2.1|lgpl + + GNU Lesser General Public License (LGPL)|GNU Library General Public License v2.1 or later + MIT License|The MIT License|MIT LICENSE|MIT|MIT license|MIT License (MIT) + + MIT License|Bouncy Castle Licence + Mozilla Public License|Mozilla Public License version 1.1|Mozilla Public License 1.1 (MPL 1.1)|MPL 1.1|Mozilla Public License Version 2.0 Mozilla Public License|MPL 2.0, and EPL 1.0 diff --git a/src/main/license/third-party-file-groupByLicense.ftl b/src/main/license/third-party-file-groupByLicense.ftl index d4759ba07d..50584ac23a 100644 --- a/src/main/license/third-party-file-groupByLicense.ftl +++ b/src/main/license/third-party-file-groupByLicense.ftl @@ -61,7 +61,7 @@ PLEASE NOTE: Some dependencies may be listed under multiple licenses if they are dual-licensed. This is especially true of anything listed as "GNU General Public Library" below, as DSpace actually does NOT allow for any dependencies that are solely released under GPL terms. For more info see: -https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines +https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines --------------------------------------------------- <#list licenseMap as e> <#assign license = e.getKey()/> From 9cf5687ceb96ac685ee7e0afd03706dcbd571e96 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Aug 2021 11:11:11 -0500 Subject: [PATCH 0115/1254] More minor LICENSES_THIRD_PARTY cleanup --- LICENSES_THIRD_PARTY | 27 +++++++++------------------ pom.xml | 2 ++ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 21eadef67d..88f694eb8e 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -18,14 +18,6 @@ dependencies that are solely released under GPL terms. For more info see: https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines --------------------------------------------------- - (MIT-style) netCDF C library license: - - * CDM core library (edu.ucar:cdm:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/documentation.htm) - * GRIB IOSP and Feature Collection (edu.ucar:grib:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/) - * HttpClient Wrappers (edu.ucar:httpservices:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/documentation.htm) - * netCDF-4 IOSP JNI connection to C library (edu.ucar:netcdf4:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/netcdf4/) - * udunits (edu.ucar:udunits:4.5.5 - http://www.unidata.ucar.edu/software/udunits//) - Apache Software License, Version 2.0: * Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net) @@ -393,15 +385,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) - CDDL+GPL License: - - * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.1 - http://jaxb.java.net/jaxb-runtime-parent/jaxb-runtime) - * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.1 - http://jaxb.java.net/jaxb-txw-parent/txw2) - Common Development and Distribution License (CDDL): * JavaBeans Activation Framework (com.sun.activation:javax.activation:1.2.0 - http://java.net/all/javax.activation/) * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.7 - http://java.net/istack-commons/istack-commons-runtime/) + * saaj-impl (com.sun.xml.messaging.saaj:saaj-impl:1.4.0-b03 - http://java.net/saaj-impl/) * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) * JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) @@ -419,10 +407,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator) * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged) * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject) + * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.1 - http://jaxb.java.net/jaxb-runtime-parent/jaxb-runtime) + * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.1 - http://jaxb.java.net/jaxb-txw-parent/txw2) * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec) * MIME streaming extension (org.jvnet.mimepull:mimepull:1.9.7 - http://mimepull.java.net) + * Extended StAX API (org.jvnet.staxex:stax-ex:1.8 - http://stax-ex.java.net/) Cordra (Version 2) License Agreement: @@ -430,11 +421,6 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * net.cnri:cnri-servlet-container-lib (net.cnri:cnri-servlet-container-lib:3.0.0 - https://gitlab.com/cnri/cnri-servlet-container) * net.cnri:cnriutil (net.cnri:cnriutil:2.0 - https://gitlab.com/cnri/cnriutil) - Dual license consisting of the CDDL v1.1 and GPL v2: - - * saaj-impl (com.sun.xml.messaging.saaj:saaj-impl:1.4.0-b03 - http://java.net/saaj-impl/) - * Extended StAX API (org.jvnet.staxex:stax-ex:1.8 - http://stax-ex.java.net/) - Eclipse Distribution License, Version 1.0: * JavaBeans Activation Framework (com.sun.activation:jakarta.activation:1.2.1 - https://github.com/eclipse-ee4j/jaf/jakarta.activation) @@ -534,6 +520,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines MIT License: * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) + * CDM core library (edu.ucar:cdm:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/documentation.htm) + * GRIB IOSP and Feature Collection (edu.ucar:grib:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/) + * HttpClient Wrappers (edu.ucar:httpservices:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/documentation.htm) + * netCDF-4 IOSP JNI connection to C library (edu.ucar:netcdf4:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/netcdf4/) + * udunits (edu.ucar:udunits:4.5.5 - http://www.unidata.ucar.edu/software/udunits//) * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk15on:1.65 - http://www.bouncycastle.org/java.html) * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.65 - http://www.bouncycastle.org/java.html) * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.65 - http://www.bouncycastle.org/java.html) diff --git a/pom.xml b/pom.xml index 9fbf3f13c2..775abc98c8 100644 --- a/pom.xml +++ b/pom.xml @@ -720,6 +720,8 @@ MIT License|The MIT License|MIT LICENSE|MIT|MIT license|MIT License (MIT) MIT License|Bouncy Castle Licence + + MIT License|(MIT-style) netCDF C library license Mozilla Public License|Mozilla Public License version 1.1|Mozilla Public License 1.1 (MPL 1.1)|MPL 1.1|Mozilla Public License Version 2.0 Mozilla Public License|MPL 2.0, and EPL 1.0 From 8febfa479d31de08fa90b643e85a47c927ca214a Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 20 Jul 2018 14:13:11 -0400 Subject: [PATCH 0116/1254] [DS-3952] First attempt. It compiles. --- .../dspace/app/requestitem/package-info.java | 21 ++ .../app/rest/converter/DSpaceConverter.java | 18 ++ .../rest/converter/RequestItemConverter.java | 69 ++++++ .../app/rest/model/RequestItemRest.java | 214 ++++++++++++++++++ .../org/dspace/app/rest/model/RestModel.java | 3 +- .../model/hateoas/RequestItemResource.java | 26 +++ .../rest/repository/DSpaceRestRepository.java | 2 + .../repository/RequestItemRepository.java | 53 +++++ 8 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RequestItemResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java b/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java new file mode 100644 index 0000000000..2e78ecba9e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java @@ -0,0 +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.requestitem; + +/** + * Feature for conveying a request that materials forbidden to the requester + * by resource policy be made available by other means. The request will be + * e-mailed to a responsible party for consideration and action. Find details + * in the user documentation under the rubric "Request a Copy". + * + * This package includes several "strategy" classes which discover responsible + * parties in various ways. See {@link RequestItemSubmitterStrategy} and the + * classes which extend it. A strategy class must be configured and identified + * as {@link RequestItemAuthorExtractor} for injection into code which requires + * Request a Copy services. + */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java index 208904eee4..ebfe1f274f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java @@ -9,9 +9,27 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.projection.Projection; +/** + * Conversion between DSpace model object and REST resource. + * + * @param type of DSpace model object (e.g. Item) + * @param type of REST resource (e.g. ItemResource) + */ public interface DSpaceConverter { + /** + * Convert a DSpace model object into its equivalent REST resource, applying + * a given projection. + * + * @param modelObject a DSpace API model object. + * @param projection + * @return a resource representing the model object. + */ R convert(M modelObject, Projection projection); + /** + * For what DSpace API model class does this converter convert? + * @return Class of model objects represented. + */ Class getModelClass(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java new file mode 100644 index 0000000000..a5c68a4237 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -0,0 +1,69 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.rest.converter; + +import java.sql.SQLException; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.requestitem.RequestItem; +import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.services.RequestService; + +/** + * Convert between {@link org.dspace.app.requestitem.RequestItem} and + * {@link org.dspace.app.rest.model.RequestItemRest}. + * + * @author Mark H. Wood + */ +@Named +public class RequestItemConverter + implements DSpaceConverter { + @Inject + protected BitstreamConverter bitstreamConverter; + + @Inject + protected ItemConverter itemConverter; + + @Inject + protected RequestItemService requestItemService; + + @Inject + protected RequestService requestService; + + @Override + public RequestItemRest convert(RequestItem requestItem, Projection projection) { + RequestItemRest requestItemRest = new RequestItemRest(); + requestItemRest.setProjection(projection); + + requestItemRest.setAccept_request(requestItem.isAccept_request()); + requestItemRest.setAllfiles(requestItem.isAllfiles()); + requestItemRest.setBitstream( + bitstreamConverter.convert(requestItem.getBitstream(), projection)); + requestItemRest.setDecision_date(requestItem.getDecision_date()); + requestItemRest.setExpires(requestItem.getExpires()); + requestItemRest.setId(requestItem.getID()); + requestItemRest.setItem( + itemConverter.convert(requestItem.getItem(), projection)); + requestItemRest.setReq_email(requestItem.getReqEmail()); + requestItemRest.setReq_message(requestItem.getReqMessage()); + requestItemRest.setReq_name(requestItem.getReqName()); + requestItemRest.setRequest_date(requestItem.getRequest_date()); + requestItemRest.setToken(requestItem.getToken()); + return requestItemRest; + } + + @Override + public Class getModelClass() { + return RequestItem.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java new file mode 100644 index 0000000000..a8dbe1dcca --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -0,0 +1,214 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 org.dspace.app.rest.converter.BitstreamConverter; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; + +/** + * Represent a user's request for a copy of an Item. + * @see org.dspace.app.requestitem + * + * @author Mark H. Wood + */ +public class RequestItemRest + extends BaseObjectRest { + public static final String NAME = "copy_request"; + + public static final String CATEGORY = RestAddressableModel.COPY_REQUEST; + + protected BitstreamRest bitstream; + protected Date decision_date; + protected Date expires; + protected ItemRest item; + protected String req_email; + protected String req_message; + protected String req_name; + protected Date request_date; + protected String token; + protected boolean accept_request; + protected boolean allfiles; + + /** + * @return the bitstream + */ + public BitstreamRest getBitstream() { + return bitstream; + } + + /** + * @param bitstream the bitstream to set + */ + public void setBitstream(BitstreamRest bitstream) { + this.bitstream = bitstream; + } + + /** + * @return the decision_date + */ + public Date getDecision_date() { + return decision_date; + } + + /** + * @param decision_date the decision_date to set + */ + public void setDecision_date(Date decision_date) { + this.decision_date = decision_date; + } + + /** + * @return the expires + */ + public Date getExpires() { + return expires; + } + + /** + * @param expires the expires to set + */ + public void setExpires(Date expires) { + this.expires = expires; + } + + /** + * @return the item + */ + public ItemRest getItem() { + return item; + } + + /** + * @param item the item to set + */ + public void setItem(ItemRest item) { + this.item = item; + } + + /** + * @return the req_email + */ + public String getReq_email() { + return req_email; + } + + /** + * @param req_email the req_email to set + */ + public void setReq_email(String req_email) { + this.req_email = req_email; + } + + /** + * @return the req_message + */ + public String getReq_message() { + return req_message; + } + + /** + * @param req_message the req_message to set + */ + public void setReq_message(String req_message) { + this.req_message = req_message; + } + + /** + * @return the req_name + */ + public String getReq_name() { + return req_name; + } + + /** + * @param req_name the req_name to set + */ + public void setReq_name(String req_name) { + this.req_name = req_name; + } + + /** + * @return the request_date + */ + public Date getRequest_date() { + return request_date; + } + + /** + * @param request_date the request_date to set + */ + public void setRequest_date(Date request_date) { + this.request_date = request_date; + } + + /** + * @return the token + */ + public String getToken() { + return token; + } + + /** + * @param token the token to set + */ + public void setToken(String token) { + this.token = token; + } + + /** + * @return the accept_request + */ + public boolean isAccept_request() { + return accept_request; + } + + /** + * @param accept_request the accept_request to set + */ + public void setAccept_request(boolean accept_request) { + this.accept_request = accept_request; + } + + /** + * @return all files? + */ + public boolean isAllfiles() { + return allfiles; + } + + /** + * @param allfiles all files? + */ + public void setAllfiles(boolean allfiles) { + this.allfiles = allfiles; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public String getType() { + return NAME; + } + + @Override + public String getTypePlural() { + return super.getTypePlural(); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java index 0b32aedf92..5b16738f40 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java @@ -14,7 +14,7 @@ import org.atteo.evo.inflector.English; /** * A REST resource directly or indirectly (in a collection) exposed must have at - * least a type attribute to facilitate deserialization + * least a type attribute to facilitate deserialization. * * @author Andrea Bollini (andrea.bollini at 4science.it) */ @@ -33,6 +33,7 @@ public interface RestModel extends Serializable { public static final String AUTHORIZATION = "authz"; public static final String VERSIONING = "versioning"; public static final String AUTHENTICATION = "authn"; + public static final String COPY_REQUEST = "copy_request"; public String getType(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RequestItemResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RequestItemResource.java new file mode 100644 index 0000000000..c58f032a58 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RequestItemResource.java @@ -0,0 +1,26 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * HAL resource for {@link RequestItemRest}. + * + * @author Mark H. Wood + */ +@RelNameDSpaceResource(RequestItemRest.NAME) +public class RequestItemResource + extends DSpaceResource { + public RequestItemResource(RequestItemRest item, Utils utils) { + super(item, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 149855c488..5fa86824b1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -40,6 +40,8 @@ import org.springframework.web.multipart.MultipartFile; * normal Spring Data Repository methods signature. * * @author Andrea Bollini (andrea.bollini at 4science.it) + * @param a REST model class (e.g. {@link ItemRest}). + * @param type used to identify an instance of the class (String, UUID, etc). */ public abstract class DSpaceRestRepository extends AbstractDSpaceRestRepository 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 new file mode 100644 index 0000000000..6ace49473c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.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.repository; + +import javax.inject.Inject; + +import org.dspace.app.requestitem.RequestItem; +import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.app.rest.converter.RequestItemConverter; +import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.model.hateoas.RequestItemResource; +import org.dspace.core.Context; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Controller; + +/** + * Controller to expose item requests. + * + * @author Mark H. Wood + */ +@Controller(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) +public class RequestItemRepository + extends DSpaceRestRepository { + @Inject + protected RequestItemService requestItemService; + + @Inject + protected RequestItemConverter requestItemConverter; + + @Override + public RequestItemRest findOne(Context context, String id) { + RequestItem requestItem = requestItemService.findByToken(context, id); + return requestItemConverter.fromModel(requestItem); + } + + @Override + public Page findAll(Context context, Pageable pageable) { + // TODO ? There is no enumerator in RequestItemService + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Class getDomainClass() { + return RequestItemRest.class; + } +} From 16031a5637a8599c2c7daefc1f19f6f247830d78 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 20 Jul 2018 16:04:11 -0400 Subject: [PATCH 0117/1254] [DS-3952] Better, fuller annotations; better documentation. --- .../app/rest/model/RequestItemRest.java | 41 ++++++++++++------- .../repository/RequestItemRepository.java | 6 +-- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index a8dbe1dcca..c4fa8b1763 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.Date; import org.dspace.app.rest.converter.BitstreamConverter; @@ -39,14 +40,16 @@ public class RequestItemRest protected boolean allfiles; /** - * @return the bitstream + * @return the bitstream requested. */ + @LinkRest + @JsonIgnore public BitstreamRest getBitstream() { return bitstream; } /** - * @param bitstream the bitstream to set + * @param bitstream the bitstream requested. */ public void setBitstream(BitstreamRest bitstream) { this.bitstream = bitstream; @@ -81,56 +84,58 @@ public class RequestItemRest } /** - * @return the item + * @return the item requested. */ + @LinkRest + @JsonIgnore public ItemRest getItem() { return item; } /** - * @param item the item to set + * @param item the item requested. */ public void setItem(ItemRest item) { this.item = item; } /** - * @return the req_email + * @return the email address of the requester. */ public String getReq_email() { return req_email; } /** - * @param req_email the req_email to set + * @param req_email the email address of the requester. */ public void setReq_email(String req_email) { this.req_email = req_email; } /** - * @return the req_message + * @return the requester's message. */ public String getReq_message() { return req_message; } /** - * @param req_message the req_message to set + * @param req_message the requester's message. */ public void setReq_message(String req_message) { this.req_message = req_message; } /** - * @return the req_name + * @return the requester's name. */ public String getReq_name() { return req_name; } /** - * @param req_name the req_name to set + * @param req_name the requester's name. */ public void setReq_name(String req_name) { this.req_name = req_name; @@ -151,42 +156,50 @@ public class RequestItemRest } /** - * @return the token + * @return the token which identifies this request. */ public String getToken() { return token; } /** - * @param token the token to set + * @param token the token which identifies this request. */ public void setToken(String token) { this.token = token; } /** - * @return the accept_request + * @return true if the request has been accepted. */ public boolean isAccept_request() { return accept_request; } /** - * @param accept_request the accept_request to set + * @param accept_request true if the request has been accepted. */ public void setAccept_request(boolean accept_request) { this.accept_request = accept_request; } /** +<<<<<<< HEAD:dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java * @return all files? +======= + * @return true if the request is for all files in the item. +>>>>>>> 7cb3f3d4b ([DS-3952] Better, fuller annotations; better documentation.):dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RequestItemRest.java */ public boolean isAllfiles() { return allfiles; } /** +<<<<<<< HEAD:dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java * @param allfiles all files? +======= + * @param allfiles true requesting all of the item's files. +>>>>>>> 7cb3f3d4b ([DS-3952] Better, fuller annotations; better documentation.):dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RequestItemRest.java */ public void setAllfiles(boolean allfiles) { this.allfiles = allfiles; 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 6ace49473c..d1bd2128aa 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 @@ -9,6 +9,7 @@ package org.dspace.app.rest.repository; import javax.inject.Inject; +import javax.inject.Named; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; @@ -18,14 +19,13 @@ import org.dspace.app.rest.model.hateoas.RequestItemResource; import org.dspace.core.Context; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Controller; /** - * Controller to expose item requests. + * Component to expose item requests. * * @author Mark H. Wood */ -@Controller(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) +@Named(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) public class RequestItemRepository extends DSpaceRestRepository { @Inject From de358067b84ee88d074c606bc16466b836a9f745 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 25 Jul 2018 12:01:24 -0400 Subject: [PATCH 0118/1254] [DS-3952] Fix style errors. --- .../org/dspace/app/rest/model/RequestItemRest.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index c4fa8b1763..b3b1075994 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -7,13 +7,9 @@ */ package org.dspace.app.rest.model; -import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.Date; -import org.dspace.app.rest.converter.BitstreamConverter; -import org.dspace.app.rest.converter.ItemConverter; -import org.dspace.content.Bitstream; -import org.dspace.content.Item; +import com.fasterxml.jackson.annotation.JsonIgnore; /** * Represent a user's request for a copy of an Item. @@ -184,22 +180,14 @@ public class RequestItemRest } /** -<<<<<<< HEAD:dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java - * @return all files? -======= * @return true if the request is for all files in the item. ->>>>>>> 7cb3f3d4b ([DS-3952] Better, fuller annotations; better documentation.):dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RequestItemRest.java */ public boolean isAllfiles() { return allfiles; } /** -<<<<<<< HEAD:dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java - * @param allfiles all files? -======= * @param allfiles true requesting all of the item's files. ->>>>>>> 7cb3f3d4b ([DS-3952] Better, fuller annotations; better documentation.):dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RequestItemRest.java */ public void setAllfiles(boolean allfiles) { this.allfiles = allfiles; From b3a40c198cb39e133de1eb5bf521c0bbbb10b343 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 31 Jul 2018 12:01:33 -0400 Subject: [PATCH 0119/1254] [DS-3952] Use injected converters instead of creating ephemeral instances; rename methods for consistency with other REST code. --- .../rest/converter/RequestItemConverter.java | 12 +-- .../app/rest/model/RequestItemRest.java | 87 +++++++++++-------- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java index a5c68a4237..dc3a6b7864 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -45,19 +45,19 @@ public class RequestItemConverter RequestItemRest requestItemRest = new RequestItemRest(); requestItemRest.setProjection(projection); - requestItemRest.setAccept_request(requestItem.isAccept_request()); + requestItemRest.setAcceptRequest(requestItem.isAccept_request()); requestItemRest.setAllfiles(requestItem.isAllfiles()); requestItemRest.setBitstream( bitstreamConverter.convert(requestItem.getBitstream(), projection)); - requestItemRest.setDecision_date(requestItem.getDecision_date()); + requestItemRest.setDecisionDate(requestItem.getDecision_date()); requestItemRest.setExpires(requestItem.getExpires()); requestItemRest.setId(requestItem.getID()); requestItemRest.setItem( itemConverter.convert(requestItem.getItem(), projection)); - requestItemRest.setReq_email(requestItem.getReqEmail()); - requestItemRest.setReq_message(requestItem.getReqMessage()); - requestItemRest.setReq_name(requestItem.getReqName()); - requestItemRest.setRequest_date(requestItem.getRequest_date()); + requestItemRest.setReqEmail(requestItem.getReqEmail()); + requestItemRest.setReqMessage(requestItem.getReqMessage()); + requestItemRest.setReqName(requestItem.getReqName()); + requestItemRest.setRequestDate(requestItem.getRequest_date()); requestItemRest.setToken(requestItem.getToken()); return requestItemRest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index b3b1075994..7d19dd2991 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -8,8 +8,12 @@ package org.dspace.app.rest.model; import java.util.Date; +import javax.inject.Inject; +import javax.inject.Named; import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.converter.BitstreamConverter; +import org.dspace.app.rest.converter.ItemConverter; /** * Represent a user's request for a copy of an Item. @@ -17,22 +21,29 @@ import com.fasterxml.jackson.annotation.JsonIgnore; * * @author Mark H. Wood */ +@Named public class RequestItemRest extends BaseObjectRest { public static final String NAME = "copy_request"; public static final String CATEGORY = RestAddressableModel.COPY_REQUEST; + @Inject + private BitstreamConverter bitstreamConverter; + + @Inject + private ItemConverter itemConverter; + protected BitstreamRest bitstream; - protected Date decision_date; + protected Date decisionDate; protected Date expires; protected ItemRest item; - protected String req_email; - protected String req_message; - protected String req_name; - protected Date request_date; + protected String reqEmail; + protected String reqMessage; + protected String reqName; + protected Date requestDate; protected String token; - protected boolean accept_request; + protected boolean acceptRequest; protected boolean allfiles; /** @@ -52,17 +63,17 @@ public class RequestItemRest } /** - * @return the decision_date + * @return the decisionDate */ - public Date getDecision_date() { - return decision_date; + public Date getDecisionDate() { + return decisionDate; } /** - * @param decision_date the decision_date to set + * @param decided the decisionDate to set */ - public void setDecision_date(Date decision_date) { - this.decision_date = decision_date; + public void setDecisionDate(Date decided) { + this.decisionDate = decided; } /** @@ -98,57 +109,57 @@ public class RequestItemRest /** * @return the email address of the requester. */ - public String getReq_email() { - return req_email; + public String getReqEmail() { + return reqEmail; } /** - * @param req_email the email address of the requester. + * @param email the email address of the requester. */ - public void setReq_email(String req_email) { - this.req_email = req_email; + public void setReqEmail(String email) { + this.reqEmail = email; } /** * @return the requester's message. */ - public String getReq_message() { - return req_message; + public String getReqMessage() { + return reqMessage; } /** - * @param req_message the requester's message. + * @param message the requester's message. */ - public void setReq_message(String req_message) { - this.req_message = req_message; + public void setReqMessage(String message) { + this.reqMessage = message; } /** * @return the requester's name. */ - public String getReq_name() { - return req_name; + public String getReqName() { + return reqName; } /** - * @param req_name the requester's name. + * @param name the requester's name. */ - public void setReq_name(String req_name) { - this.req_name = req_name; + public void setReqName(String name) { + this.reqName = name; } /** - * @return the request_date + * @return the requestDate */ - public Date getRequest_date() { - return request_date; + public Date getRequestDate() { + return requestDate; } /** - * @param request_date the request_date to set + * @param requested the requestDate to set */ - public void setRequest_date(Date request_date) { - this.request_date = request_date; + public void setRequestDate(Date requested) { + this.requestDate = requested; } /** @@ -168,15 +179,15 @@ public class RequestItemRest /** * @return true if the request has been accepted. */ - public boolean isAccept_request() { - return accept_request; + public boolean isAcceptRequest() { + return acceptRequest; } /** - * @param accept_request true if the request has been accepted. + * @param accepted true if the request has been accepted. */ - public void setAccept_request(boolean accept_request) { - this.accept_request = accept_request; + public void setAcceptRequest(boolean accepted) { + this.acceptRequest = accepted; } /** From 48dd36f165dd377a6bc44109bcc48f904e3ff18c Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 3 Aug 2018 16:39:11 -0400 Subject: [PATCH 0120/1254] [DS-3952] Conform to endpoint naming requirements. --- .../main/java/org/dspace/app/rest/model/RequestItemRest.java | 4 ++-- .../src/main/java/org/dspace/app/rest/model/RestModel.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 7d19dd2991..73cc49b20f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -24,9 +24,9 @@ import org.dspace.app.rest.converter.ItemConverter; @Named public class RequestItemRest extends BaseObjectRest { - public static final String NAME = "copy_request"; + public static final String NAME = "copyRequest"; - public static final String CATEGORY = RestAddressableModel.COPY_REQUEST; + public static final String CATEGORY = RestAddressableModel.TOOLS; @Inject private BitstreamConverter bitstreamConverter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java index 5b16738f40..ecfe44e0ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java @@ -34,6 +34,7 @@ public interface RestModel extends Serializable { public static final String VERSIONING = "versioning"; public static final String AUTHENTICATION = "authn"; public static final String COPY_REQUEST = "copy_request"; + public static final String TOOLS = "tools"; public String getType(); From 5048105cf6818061589017376f1d7c912c1f4aee Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 3 Aug 2018 16:58:15 -0400 Subject: [PATCH 0121/1254] [DS-3952] Conform better to established endpoint naming conventions. --- .../main/java/org/dspace/app/rest/model/RequestItemRest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 73cc49b20f..a6ebae7c21 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -24,7 +24,7 @@ import org.dspace.app.rest.converter.ItemConverter; @Named public class RequestItemRest extends BaseObjectRest { - public static final String NAME = "copyRequest"; + public static final String NAME = "copyrequest"; public static final String CATEGORY = RestAddressableModel.TOOLS; From 0a1babdb5485270765b3668b892e599033b97187 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 7 Aug 2018 15:01:20 -0400 Subject: [PATCH 0122/1254] [DS-3952] First try at writability, untested. --- .../authority/AuthoritySearchService.java | 9 +++++++ .../authority/service/AuthorityService.java | 13 ++++++--- .../repository/RequestItemRepository.java | 27 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthoritySearchService.java b/dspace-api/src/main/java/org/dspace/authority/AuthoritySearchService.java index 3c956e0009..c0373e9442 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthoritySearchService.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthoritySearchService.java @@ -16,6 +16,8 @@ import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.response.QueryResponse; /** + * Manage queries of the Solr authority core. + * * @author Antoine Snyers (antoine at atmire.com) * @author Kevin Van de Velde (kevin at atmire dot com) * @author Ben Bosman (ben at atmire dot com) @@ -26,6 +28,13 @@ public interface AuthoritySearchService { public QueryResponse search(SolrQuery query) throws SolrServerException, MalformedURLException, IOException; + /** + * Retrieves all the metadata fields which are indexed in the authority control. + * + * @return names of indexed fields. + * @throws SolrServerException passed through. + * @throws MalformedURLException passed through. + */ public List getAllIndexedMetadataFields() throws Exception; } diff --git a/dspace-api/src/main/java/org/dspace/authority/service/AuthorityService.java b/dspace-api/src/main/java/org/dspace/authority/service/AuthorityService.java index 42cbe2d686..21822993ef 100644 --- a/dspace-api/src/main/java/org/dspace/authority/service/AuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/authority/service/AuthorityService.java @@ -14,14 +14,21 @@ import org.dspace.content.Item; import org.dspace.core.Context; /** - * Service interface class for the Metadata Authority - * The implementation of this class is responsible for all business logic calls for the Metadata Authority and is - * autowired by spring + * Service interface class for the Metadata Authority. + * The implementation of this class is responsible for all business logic calls + * for the Metadata Authority and is autowired by Spring. * * @author kevinvandevelde at atmire.com */ public interface AuthorityService { + /** + * Add an {@link Item} to the authority index. + * @param context current DSpace session. + * @param item the Item to be added. + * @throws SQLException passed through. + * @throws AuthorizeException passed through. + */ public void indexItem(Context context, Item item) throws SQLException, AuthorizeException; public boolean isConfigurationValid(); 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 d1bd2128aa..47f6093456 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 @@ -8,15 +8,20 @@ package org.dspace.app.rest.repository; +import java.sql.SQLException; import javax.inject.Inject; import javax.inject.Named; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.model.RequestItemRest; import org.dspace.app.rest.model.hateoas.RequestItemResource; +import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -28,6 +33,8 @@ import org.springframework.data.domain.Pageable; @Named(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) public class RequestItemRepository extends DSpaceRestRepository { + private static final Logger LOG = LoggerFactory.getLogger(RequestItemRepository.class); + @Inject protected RequestItemService requestItemService; @@ -46,6 +53,26 @@ public class RequestItemRepository throw new UnsupportedOperationException("Not supported yet."); } + @Override + public RequestItemRest save(Context context, RequestItemRest ri) { + try { + requestItemService.createRequest(context, ri.getBitstream(), + ri.getItem(), ri.isAllfiles(), ri.getReqEmail(), + ri.getReqName(), ri.getReqMessage()); + } catch (SQLException ex) { + LOG.error("New RequestItem not saved.", ex); + // TODO how to inform caller that request was not saved? + } + return ri; + } + + // NOTICE: there is no service method for this -- requests are never deleted? + @Override + public void delete(Context context, String token) + throws AuthorizeException, RepositoryMethodNotImplementedException { + throw new RepositoryMethodNotImplementedException("RequestItemRest", "delete"); + } + @Override public Class getDomainClass() { return RequestItemRest.class; From 7e1bf53b401b720f3c04ef3056a85084ccb5d459 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 13 Sep 2018 10:52:05 -0400 Subject: [PATCH 0123/1254] [DS-3952] Replace standard @Inject with Spring-proprietary @Autowired for consistency. --- .../app/rest/converter/RequestItemConverter.java | 12 +++++------- .../org/dspace/app/rest/model/RequestItemRest.java | 6 +++--- .../app/rest/repository/RequestItemRepository.java | 6 +++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java index dc3a6b7864..ab63ea4816 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -8,16 +8,14 @@ package org.dspace.app.rest.converter; -import java.sql.SQLException; -import javax.inject.Inject; import javax.inject.Named; -import javax.servlet.http.HttpServletRequest; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.model.RequestItemRest; import org.dspace.app.rest.projection.Projection; import org.dspace.services.RequestService; +import org.springframework.beans.factory.annotation.Autowired; /** * Convert between {@link org.dspace.app.requestitem.RequestItem} and @@ -28,16 +26,16 @@ import org.dspace.services.RequestService; @Named public class RequestItemConverter implements DSpaceConverter { - @Inject + @Autowired(required=true) protected BitstreamConverter bitstreamConverter; - @Inject + @Autowired(required=true) protected ItemConverter itemConverter; - @Inject + @Autowired(required=true) protected RequestItemService requestItemService; - @Inject + @Autowired(required=true) protected RequestService requestService; @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index a6ebae7c21..0f7747dee2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -8,12 +8,12 @@ package org.dspace.app.rest.model; import java.util.Date; -import javax.inject.Inject; import javax.inject.Named; import com.fasterxml.jackson.annotation.JsonIgnore; import org.dspace.app.rest.converter.BitstreamConverter; import org.dspace.app.rest.converter.ItemConverter; +import org.springframework.beans.factory.annotation.Autowired; /** * Represent a user's request for a copy of an Item. @@ -28,10 +28,10 @@ public class RequestItemRest public static final String CATEGORY = RestAddressableModel.TOOLS; - @Inject + @Autowired(required=true) private BitstreamConverter bitstreamConverter; - @Inject + @Autowired(required=true) private ItemConverter itemConverter; protected BitstreamRest bitstream; 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 47f6093456..28f4d64d3b 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 @@ -9,7 +9,6 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.inject.Inject; import javax.inject.Named; import org.dspace.app.requestitem.RequestItem; @@ -22,6 +21,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -35,10 +35,10 @@ public class RequestItemRepository extends DSpaceRestRepository { private static final Logger LOG = LoggerFactory.getLogger(RequestItemRepository.class); - @Inject + @Autowired(required=true) protected RequestItemService requestItemService; - @Inject + @Autowired(required=true) protected RequestItemConverter requestItemConverter; @Override From 7751b5c75fefddd9cf032f9517cc3f9b2882eba3 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 13 Sep 2018 15:00:13 -0400 Subject: [PATCH 0124/1254] [DS-3952] Satisfy checkstyle again. I think I have my IDE configured properly for this now. --- .../org/dspace/app/rest/converter/RequestItemConverter.java | 6 +++--- .../java/org/dspace/app/rest/model/RequestItemRest.java | 4 ++-- .../dspace/app/rest/repository/RequestItemRepository.java | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java index ab63ea4816..d2c5cc547b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -29,13 +29,13 @@ public class RequestItemConverter @Autowired(required=true) protected BitstreamConverter bitstreamConverter; - @Autowired(required=true) + @Autowired(required = true) protected ItemConverter itemConverter; - @Autowired(required=true) + @Autowired(required = true) protected RequestItemService requestItemService; - @Autowired(required=true) + @Autowired(required = true) protected RequestService requestService; @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 0f7747dee2..4e91d4c9b0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -28,10 +28,10 @@ public class RequestItemRest public static final String CATEGORY = RestAddressableModel.TOOLS; - @Autowired(required=true) + @Autowired(required = true) private BitstreamConverter bitstreamConverter; - @Autowired(required=true) + @Autowired(required = true) private ItemConverter itemConverter; protected BitstreamRest bitstream; 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 28f4d64d3b..90f8952673 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 @@ -35,10 +35,10 @@ public class RequestItemRepository extends DSpaceRestRepository { private static final Logger LOG = LoggerFactory.getLogger(RequestItemRepository.class); - @Autowired(required=true) + @Autowired(required = true) protected RequestItemService requestItemService; - @Autowired(required=true) + @Autowired(required = true) protected RequestItemConverter requestItemConverter; @Override From 619e6d4eacfd4032e386520313a114ab6e7705a8 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 14 Sep 2018 09:15:50 -0400 Subject: [PATCH 0125/1254] [DS-3952] Throw a somewhat specific exception if we cannot store a request. --- .../org/dspace/app/rest/repository/RequestItemRepository.java | 3 ++- 1 file changed, 2 insertions(+), 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 90f8952673..6808a98fb4 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 @@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.jdbc.UncategorizedSQLException; /** * Component to expose item requests. @@ -61,7 +62,7 @@ public class RequestItemRepository ri.getReqName(), ri.getReqMessage()); } catch (SQLException ex) { LOG.error("New RequestItem not saved.", ex); - // TODO how to inform caller that request was not saved? + throw new UncategorizedSQLException("New RequestItem save", null, ex); } return ri; } From 1590e72f0f3a46a71e2952762439705d388b0825 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Sep 2018 09:12:11 -0400 Subject: [PATCH 0126/1254] [DS-3952] Begin building integration tests. --- .../dspace/builder/RequestItemBuilder.java | 91 ++++++++++ .../app/rest/matcher/RequestCopyMatcher.java | 45 +++++ .../repository/RequestItemRepositoryIT.java | 170 ++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java new file mode 100644 index 0000000000..813a44a8f0 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.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.builder; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; + +import org.dspace.app.requestitem.RequestItem; +import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Manage the creation and cleanup of {@link RequestItem}s for testing. + * + * @author Mark H. Wood + */ +public class RequestItemBuilder + extends AbstractBuilder { + @Autowired(required = true) + protected RequestItemService requestItemService; + + public static final String REQ_EMAIL = "jsmith@example.com"; + public static final String REQ_NAME = "John Smith"; + public static final String REQ_MESSAGE = "Please send me a copy of this."; + public static final String REQ_PATH = "test/file"; + + private Community community; + private Collection collection; + private Item item; + private Bitstream bitstream; + + public RequestItemBuilder(Context context) { + super(context); + } + + @Override + public void cleanup() + throws Exception { + bitstreamService.delete(context, bitstream); + itemService.delete(context, item); + collectionService.delete(context, collection); + communityService.delete(context, community); + } + + @Override + public RequestItem build() { + InputStream is = new ByteArrayInputStream("".getBytes()); + CommunityBuilder communityBuilder = CommunityBuilder.createCommunity(context); + community = communityBuilder.build(); + CollectionBuilder collectionBuilder = CollectionBuilder.createCollection(context, + community); + collection = collectionBuilder.build(); + ItemBuilder itemBuilder = ItemBuilder.createItem(context, collection); + item = itemBuilder.build(); + String token; + try { + BitstreamBuilder bitstreamBuilder = BitstreamBuilder.createBitstream(context, item, is); + bitstream = bitstreamBuilder.build(); + token = requestItemService.createRequest(context, + bitstream, item, true, REQ_EMAIL, REQ_NAME, REQ_MESSAGE); + } catch (SQLException | AuthorizeException | IOException ex) { + throw new RuntimeException(ex); + } + return requestItemService.findByToken(context, token); + } + + @Override + public void delete(Context context, RequestItem request) + throws Exception { + // N.B. RequestItem cannot be deleted. + } + + @Override + protected RequestItemService getService() { + return requestItemService; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java new file mode 100644 index 0000000000..3d194fc18d --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -0,0 +1,45 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.dspace.app.requestitem.RequestItem; +import org.dspace.builder.RequestItemBuilder; +import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.repository.RequestItemRepositoryIT; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +/** + * + * @author Mark H. Wood + */ +public class RequestCopyMatcher { + private RequestCopyMatcher() { } + + public static Matcher matchRequestCopy(RequestItem request) { + return allOf( + hasJsonPath("$.bitstream", Matchers.not(Matchers.empty())), + hasJsonPath("$.item", Matchers.not(Matchers.empty())), + hasJsonPath("$.allFiles", is("true")), + hasJsonPath("$.reqEmail", is(RequestItemBuilder.REQ_EMAIL)), + hasJsonPath("$.reqName", is(RequestItemBuilder.REQ_NAME)), + hasJsonPath("$.reqMessage", is(RequestItemBuilder.REQ_MESSAGE)), + hasJsonPath("$.type", is(RequestItemRest.NAME)), + hasJsonPath("$._embedded.schema", Matchers.not(Matchers.empty())), + hasJsonPath("$._links.schema.href", + Matchers.containsString(RequestItemRepositoryIT.URI_ROOT)), + hasJsonPath("$._links.self.href", + Matchers.containsString(RequestItemRepositoryIT.URI_ROOT)) + ); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java new file mode 100644 index 0000000000..8270c56fe1 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java @@ -0,0 +1,170 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +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.requestitem.RequestItem; +import org.dspace.builder.RequestItemBuilder; +import org.dspace.app.rest.matcher.RequestCopyMatcher; +import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * + * @author Mark H. Wood + */ +public class RequestItemRepositoryIT + extends AbstractControllerIntegrationTest { + public static final String URI_ROOT = "/api/" + + RequestItemRest.CATEGORY + '/' + + RequestItemRest.NAME; + + public RequestItemRepositoryIT() { + } + +/* + @BeforeClass + public static void setUpClass() { + } +*/ + +/* + @AfterClass + public static void tearDownClass() { + } +*/ + +/* + @Before + public void setUp() { + } +*/ + +/* + @After + public void tearDown() { + } +*/ + + /** + * Test of findOne method, of class RequestItemRepository. + * @throws java.lang.Exception passed through. + */ + @Test + public void testFindOne() + throws Exception { + System.out.println("findOne"); + RequestItem request = new RequestItemBuilder(context).build(); + + final String uri = URI_ROOT + '/' + + request.getToken(); + getClient().perform(get(uri)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + RequestCopyMatcher.matchRequestCopy(request)))); + } + + /** + * Test of findAll method, of class RequestItemRepository. + */ +/* + @Test + public void testFindAll() + { + System.out.println("findAll"); + Context context = null; + Pageable pageable = null; + RequestItemRepository instance = new RequestItemRepository(); + Page expResult = null; + Page result = instance.findAll(context, pageable); + assertEquals(expResult, result); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } +*/ + + /** + * Test of save method, of class RequestItemRepository. + */ +/* + @Test + public void testSave() + { + System.out.println("save"); + Context context = null; + RequestItemRest ri = null; + RequestItemRepository instance = new RequestItemRepository(); + RequestItemRest expResult = null; + RequestItemRest result = instance.save(context, ri); + assertEquals(expResult, result); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } +*/ + + /** + * Test of delete method, of class RequestItemRepository. + */ +/* + @Test + public void testDelete() + throws Exception + { + System.out.println("delete"); + Context context = null; + String token = ""; + RequestItemRepository instance = new RequestItemRepository(); + instance.delete(context, token); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } +*/ + + /** + * Test of getDomainClass method, of class RequestItemRepository. + */ +/* + @Test + public void testGetDomainClass() + { + System.out.println("getDomainClass"); + RequestItemRepository instance = new RequestItemRepository(); + Class expResult = null; + Class result = instance.getDomainClass(); + assertEquals(expResult, result); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } +*/ + + /** + * Test of wrapResource method, of class RequestItemRepository. + */ +/* + @Test + public void testWrapResource() + { + System.out.println("wrapResource"); + RequestItemRest model = null; + String[] rels = null; + RequestItemRepository instance = new RequestItemRepository(); + RequestItemResource expResult = null; + RequestItemResource result = instance.wrapResource(model, rels); + assertEquals(expResult, result); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } +*/ +} From fae1c5622d8112508187adce03f23ae1b70a14b1 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Sep 2018 10:35:23 -0400 Subject: [PATCH 0127/1254] [DS-3952] AbstractBuilder should know about RequestItem since there is a builder for it now. --- .../test/java/org/dspace/builder/AbstractBuilder.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 1f8a9e4a5b..d761bd8b21 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -8,10 +8,14 @@ package org.dspace.builder; import java.sql.SQLException; +import java.util.LinkedList; import java.util.List; import org.apache.commons.collections4.CollectionUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.requestitem.factory.RequestItemServiceFactory; +import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; @@ -89,6 +93,7 @@ public abstract class AbstractBuilder { static RelationshipTypeService relationshipTypeService; static EntityTypeService entityTypeService; static ProcessService processService; + static RequestItemService requestItemService; protected Context context; @@ -101,7 +106,7 @@ public abstract class AbstractBuilder { /** * log4j category */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(AbstractDSpaceObjectBuilder.class); + private static final Logger log = LogManager.getLogger(); protected AbstractBuilder(Context context) { this.context = context; @@ -136,6 +141,7 @@ public abstract class AbstractBuilder { relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); processService = ScriptServiceFactory.getInstance().getProcessService(); + requestItemService = RequestItemServiceFactory.getInstance().getRequestItemService(); // Temporarily disabled claimedTaskService = XmlWorkflowServiceFactory.getInstance().getClaimedTaskService(); @@ -172,7 +178,7 @@ public abstract class AbstractBuilder { relationshipTypeService = null; entityTypeService = null; processService = null; - + requestItemService = null; } public static void cleanupObjects() throws Exception { From 797e998739acc091e28c9d4b8e07dd0ad23ed54d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Sep 2018 10:36:53 -0400 Subject: [PATCH 0128/1254] [DS-3952] Use service from superclass, tighten code. --- .../org/dspace/builder/RequestItemBuilder.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index 813a44a8f0..0ef85d6edf 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -21,7 +21,6 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; /** * Manage the creation and cleanup of {@link RequestItem}s for testing. @@ -30,9 +29,6 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class RequestItemBuilder extends AbstractBuilder { - @Autowired(required = true) - protected RequestItemService requestItemService; - public static final String REQ_EMAIL = "jsmith@example.com"; public static final String REQ_NAME = "John Smith"; public static final String REQ_MESSAGE = "Please send me a copy of this."; @@ -58,7 +54,7 @@ public class RequestItemBuilder @Override public RequestItem build() { - InputStream is = new ByteArrayInputStream("".getBytes()); + // Build all the other stuff we need, to request an Item. CommunityBuilder communityBuilder = CommunityBuilder.createCommunity(context); community = communityBuilder.build(); CollectionBuilder collectionBuilder = CollectionBuilder.createCollection(context, @@ -66,15 +62,20 @@ public class RequestItemBuilder collection = collectionBuilder.build(); ItemBuilder itemBuilder = ItemBuilder.createItem(context, collection); item = itemBuilder.build(); + + // Request a copy of the Item that we just built. String token; - try { - BitstreamBuilder bitstreamBuilder = BitstreamBuilder.createBitstream(context, item, is); + try (InputStream is = new ByteArrayInputStream("".getBytes());) { + BitstreamBuilder bitstreamBuilder + = BitstreamBuilder.createBitstream(context, item, is); bitstream = bitstreamBuilder.build(); token = requestItemService.createRequest(context, bitstream, item, true, REQ_EMAIL, REQ_NAME, REQ_MESSAGE); } catch (SQLException | AuthorizeException | IOException ex) { throw new RuntimeException(ex); } + + // Return the request. return requestItemService.findByToken(context, token); } From 1b98d735df3da82edeade620d654d4539742bc01 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Sep 2018 10:37:55 -0400 Subject: [PATCH 0129/1254] [DS-3952] Run test with full privileges. --- .../dspace/app/rest/repository/RequestItemRepositoryIT.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java index 8270c56fe1..fa40e227e1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java @@ -65,6 +65,8 @@ public class RequestItemRepositoryIT public void testFindOne() throws Exception { System.out.println("findOne"); + context.turnOffAuthorisationSystem(); + RequestItem request = new RequestItemBuilder(context).build(); final String uri = URI_ROOT + '/' @@ -74,6 +76,8 @@ public class RequestItemRepositoryIT .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( RequestCopyMatcher.matchRequestCopy(request)))); + + context.restoreAuthSystemState(); } /** From e1986ad678720cc2e0827eb85cd34316c84fbfd1 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Sep 2018 17:08:36 -0400 Subject: [PATCH 0130/1254] [DS-3952] Switch to Spring proprietary annotations, and other cleanup. --- .../app/rest/model/RequestItemRest.java | 6 +- .../repository/RequestItemRepository.java | 4 +- .../rest/converter/RequestItemConverter.java | 93 +++++++++++++++++++ 3 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 4e91d4c9b0..0d3ad65190 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -8,12 +8,12 @@ package org.dspace.app.rest.model; import java.util.Date; -import javax.inject.Named; import com.fasterxml.jackson.annotation.JsonIgnore; import org.dspace.app.rest.converter.BitstreamConverter; import org.dspace.app.rest.converter.ItemConverter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; /** * Represent a user's request for a copy of an Item. @@ -21,9 +21,9 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author Mark H. Wood */ -@Named +@Component public class RequestItemRest - extends BaseObjectRest { + extends BaseObjectRest { public static final String NAME = "copyrequest"; public static final String CATEGORY = RestAddressableModel.TOOLS; 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 6808a98fb4..fdaf2caac4 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 @@ -9,7 +9,6 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import javax.inject.Named; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; @@ -25,13 +24,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.jdbc.UncategorizedSQLException; +import org.springframework.stereotype.Component; /** * Component to expose item requests. * * @author Mark H. Wood */ -@Named(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) +@Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) public class RequestItemRepository extends DSpaceRestRepository { private static final Logger LOG = LoggerFactory.getLogger(RequestItemRepository.class); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java new file mode 100644 index 0000000000..f999ad34e1 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.sql.SQLException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.requestitem.RequestItem; +import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.dspace.services.RequestService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Convert between {@link org.dspace.app.requestitem.RequestItem} and + * {@link org.dspace.app.rest.model.RequestItemRest}. + * + * @author Mark H. Wood + */ +@Component +public class RequestItemConverter + extends DSpaceConverter { + private static final Logger LOG = LoggerFactory.getLogger(RequestItemConverter.class); + + @Autowired(required = true) + protected BitstreamConverter bitstreamConverter; + + @Autowired(required = true) + protected ItemConverter itemConverter; + + @Autowired(required = true) + protected RequestItemService requestItemService; + + @Autowired(required = true) + protected RequestService requestService; + + @Override + public RequestItemRest fromModel(RequestItem requestItem) { + RequestItemRest requestItemRest = new RequestItemRest(); + requestItemRest.setAcceptRequest(requestItem.isAccept_request()); + requestItemRest.setAllfiles(requestItem.isAllfiles()); + requestItemRest.setBitstream(bitstreamConverter.fromModel(requestItem.getBitstream())); + requestItemRest.setDecisionDate(requestItem.getDecision_date()); + requestItemRest.setExpires(requestItem.getExpires()); + requestItemRest.setId(requestItem.getID()); + requestItemRest.setItem(itemConverter.fromModel(requestItem.getItem())); + requestItemRest.setReqEmail(requestItem.getReqEmail()); + requestItemRest.setReqMessage(requestItem.getReqMessage()); + requestItemRest.setReqName(requestItem.getReqName()); + requestItemRest.setRequestDate(requestItem.getRequest_date()); + requestItemRest.setToken(requestItem.getToken()); + return requestItemRest; + } + + @Override + public RequestItem toModel(RequestItemRest obj) { + HttpServletRequest request = requestService.getCurrentRequest().getHttpServletRequest(); + Context context = ContextUtil.obtainContext(request); + + // Did we receive a token? That is: are we updating an existing request? + String token = obj.getToken(); + if (StringUtils.isBlank(token)) { // No token, so create a new request. + try { + token = requestItemService.createRequest(context, + obj.getBitstream(), + obj.getItem(), + obj.isAllfiles(), + obj.getReqEmail(), + obj.getReqName(), + obj.getReqMessage()); + } catch (SQLException ex) { + LOG.error(ex.getMessage(), ex); + } + } // Otherwise (we have a token) this is updating an existing request. + + RequestItem requestItem = requestItemService.findByToken(context, token); + requestItem.setAccept_request(obj.isAcceptRequest()); + requestItem.setDecision_date(obj.getDecisionDate()); + return requestItem; + } +} From a37c658931b86476c61916f57c22fdb278e5284b Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Sep 2018 17:10:14 -0400 Subject: [PATCH 0131/1254] [DS-3952] Debug the integration tests (unfinished). --- .../dspace/builder/RequestItemBuilder.java | 68 ++++++++----------- .../dspace/builder/RequestItemHelperDAO.java | 40 +++++++++++ .../repository/RequestItemRepository.java | 55 +++++++++++---- .../app/rest/matcher/RequestCopyMatcher.java | 11 ++- .../repository/RequestItemRepositoryIT.java | 55 +++++++++++++-- 5 files changed, 163 insertions(+), 66 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index 0ef85d6edf..5ada709b75 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -8,19 +8,15 @@ package org.dspace.builder; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; import java.sql.SQLException; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; -import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; -import org.dspace.content.Collection; -import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.core.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Manage the creation and cleanup of {@link RequestItem}s for testing. @@ -29,60 +25,54 @@ import org.dspace.core.Context; */ public class RequestItemBuilder extends AbstractBuilder { + private static final Logger LOG = LoggerFactory.getLogger(RequestItemBuilder.class); + public static final String REQ_EMAIL = "jsmith@example.com"; public static final String REQ_NAME = "John Smith"; public static final String REQ_MESSAGE = "Please send me a copy of this."; public static final String REQ_PATH = "test/file"; - private Community community; - private Collection collection; - private Item item; - private Bitstream bitstream; + private RequestItem requestItem; - public RequestItemBuilder(Context context) { + protected RequestItemBuilder(Context context) { super(context); } @Override public void cleanup() throws Exception { - bitstreamService.delete(context, bitstream); - itemService.delete(context, item); - collectionService.delete(context, collection); - communityService.delete(context, community); + LOG.info("cleanup()"); + delete(context,requestItem); + } + + public static RequestItemBuilder createRequestItem(Context ctx, Item item, + Bitstream bitstream) { + RequestItemBuilder builder = new RequestItemBuilder(ctx); + return builder.create(item, bitstream); + } + + private RequestItemBuilder create(Item item, Bitstream bitstream) { + String token; + try { + token = requestItemService.createRequest(context, bitstream, + item, true, REQ_EMAIL, REQ_NAME, REQ_MESSAGE); + } catch (SQLException ex) { + return handleException(ex); + } + this.requestItem = requestItemService.findByToken(context, token); + return this; } @Override public RequestItem build() { - // Build all the other stuff we need, to request an Item. - CommunityBuilder communityBuilder = CommunityBuilder.createCommunity(context); - community = communityBuilder.build(); - CollectionBuilder collectionBuilder = CollectionBuilder.createCollection(context, - community); - collection = collectionBuilder.build(); - ItemBuilder itemBuilder = ItemBuilder.createItem(context, collection); - item = itemBuilder.build(); - - // Request a copy of the Item that we just built. - String token; - try (InputStream is = new ByteArrayInputStream("".getBytes());) { - BitstreamBuilder bitstreamBuilder - = BitstreamBuilder.createBitstream(context, item, is); - bitstream = bitstreamBuilder.build(); - token = requestItemService.createRequest(context, - bitstream, item, true, REQ_EMAIL, REQ_NAME, REQ_MESSAGE); - } catch (SQLException | AuthorizeException | IOException ex) { - throw new RuntimeException(ex); - } - - // Return the request. - return requestItemService.findByToken(context, token); + // Nothing to build. + return requestItem; } @Override public void delete(Context context, RequestItem request) throws Exception { - // N.B. RequestItem cannot be deleted. + new RequestItemHelperDAO().delete(context, request.getToken()); } @Override diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java new file mode 100644 index 0000000000..d6128ca15a --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java @@ -0,0 +1,40 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.rest.builder; + +import java.sql.SQLException; +import javax.persistence.Query; + +import org.dspace.app.requestitem.RequestItem; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Supply missing "delete" operation on RequestItem, to support testing. + * + * @author Mark H. Wood + */ +public class RequestItemHelperDAO extends AbstractHibernateDAO { + Logger LOG = LoggerFactory.getLogger(RequestItemHelperDAO.class); + + void delete(Context context, String token) + throws SQLException { + LOG.info("delete request with token {}", token); + + Query delete = createQuery(context, "DELETE FROM " + + RequestItem.class.getSimpleName() + + " WHERE token = :token"); + delete.setParameter("token", token); + int howmany = delete.executeUpdate(); + + LOG.info("Deleted {} requests", howmany); + } +} 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 fdaf2caac4..7067fe30d6 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 @@ -8,22 +8,29 @@ package org.dspace.app.rest.repository; +import java.io.IOException; import java.sql.SQLException; +import java.util.UUID; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RequestItemRest; -import org.dspace.app.rest.model.hateoas.RequestItemResource; +import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.jdbc.UncategorizedSQLException; import org.springframework.stereotype.Component; /** @@ -34,18 +41,22 @@ import org.springframework.stereotype.Component; @Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) public class RequestItemRepository extends DSpaceRestRepository { - private static final Logger LOG = LoggerFactory.getLogger(RequestItemRepository.class); - @Autowired(required = true) protected RequestItemService requestItemService; + @Autowired(required = true) + protected BitstreamService bitstreamService; + + @Autowired(required = true) + protected ItemService itemService; + @Autowired(required = true) protected RequestItemConverter requestItemConverter; @Override public RequestItemRest findOne(Context context, String id) { RequestItem requestItem = requestItemService.findByToken(context, id); - return requestItemConverter.fromModel(requestItem); + return requestItemConverter.convert(requestItem, new DefaultProjection()); } @Override @@ -55,16 +66,30 @@ public class RequestItemRepository } @Override - public RequestItemRest save(Context context, RequestItemRest ri) { + protected RequestItemRest createAndReturn(Context context) + throws SQLException { + HttpServletRequest httpRequest = getRequestService().getCurrentRequest() + .getHttpServletRequest(); + ObjectMapper mapper = new ObjectMapper(); + RequestItemRest requestItemRest = null; try { - requestItemService.createRequest(context, ri.getBitstream(), - ri.getItem(), ri.isAllfiles(), ri.getReqEmail(), - ri.getReqName(), ri.getReqMessage()); - } catch (SQLException ex) { - LOG.error("New RequestItem not saved.", ex); - throw new UncategorizedSQLException("New RequestItem save", null, ex); + ServletInputStream input = httpRequest.getInputStream(); + requestItemRest = mapper.readValue(input, RequestItemRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body", e1); } - return ri; + Bitstream bitstream = bitstreamService.find(context, + UUID.fromString(requestItemRest.getBitstream().getUuid())); + Item item = itemService.find(context, + UUID.fromString(requestItemRest.getItem().getUuid())); + String itemRequestId = requestItemService.createRequest(context, + bitstream, item, + requestItemRest.isAllfiles(), + requestItemRest.getReqEmail(), + requestItemRest.getReqName(), + requestItemRest.getReqMessage()); + requestItemRest.setToken(itemRequestId); + return requestItemRest; } // NOTICE: there is no service method for this -- requests are never deleted? diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java index 3d194fc18d..b6f151b075 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -13,9 +13,8 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; import org.dspace.app.requestitem.RequestItem; -import org.dspace.builder.RequestItemBuilder; -import org.dspace.app.rest.model.RequestItemRest; import org.dspace.app.rest.repository.RequestItemRepositoryIT; +import org.dspace.app.rest.model.RequestItemRest; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -30,10 +29,10 @@ public class RequestCopyMatcher { return allOf( hasJsonPath("$.bitstream", Matchers.not(Matchers.empty())), hasJsonPath("$.item", Matchers.not(Matchers.empty())), - hasJsonPath("$.allFiles", is("true")), - hasJsonPath("$.reqEmail", is(RequestItemBuilder.REQ_EMAIL)), - hasJsonPath("$.reqName", is(RequestItemBuilder.REQ_NAME)), - hasJsonPath("$.reqMessage", is(RequestItemBuilder.REQ_MESSAGE)), + hasJsonPath("$.allFiles", is(String.valueOf(request.isAllfiles()))), + hasJsonPath("$.reqEmail", is(request.getReqEmail())), + hasJsonPath("$.reqName", is(request.getReqName())), + hasJsonPath("$.reqMessage", is(request.getReqMessage())), hasJsonPath("$.type", is(RequestItemRest.NAME)), hasJsonPath("$._embedded.schema", Matchers.not(Matchers.empty())), hasJsonPath("$._links.schema.href", diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java index fa40e227e1..418cd21602 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java @@ -12,13 +12,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + import org.dspace.app.requestitem.RequestItem; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.builder.RequestItemBuilder; import org.dspace.app.rest.matcher.RequestCopyMatcher; import org.dspace.app.rest.model.RequestItemRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; /** * @@ -26,12 +40,10 @@ import org.junit.Test; */ public class RequestItemRepositoryIT extends AbstractControllerIntegrationTest { + /** Where to find {@link RequestItem}s in the local URL namespace. */ public static final String URI_ROOT = "/api/" + RequestItemRest.CATEGORY + '/' - + RequestItemRest.NAME; - - public RequestItemRepositoryIT() { - } + + RequestItemRest.NAME + 's'; /* @BeforeClass @@ -65,18 +77,49 @@ public class RequestItemRepositoryIT public void testFindOne() throws Exception { System.out.println("findOne"); + context.turnOffAuthorisationSystem(); - RequestItem request = new RequestItemBuilder(context).build(); + // Create necessary supporting objects. + Community community = CommunityBuilder.createCommunity(context) + .build(); + Collection collection = CollectionBuilder.createCollection(context, community) + .build(); + Item item = ItemBuilder.createItem(context, collection) + .build(); + InputStream is = new ByteArrayInputStream(new byte[0]); + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) + .build(); + // Create a request. + RequestItem request = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .build(); + + // Test: was it created correctly? final String uri = URI_ROOT + '/' + request.getToken(); +/* getClient().perform(get(uri)) - .andExpect(status().isOk()) + .andExpect(status().isOk()) // Can we find it? .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( RequestCopyMatcher.matchRequestCopy(request)))); +*/ + //try { + MockMvc client = getClient(); + MockHttpServletRequestBuilder get = get(uri); + ResultActions response = client.perform(get); + response.andExpect(status().isOk()); // Can we find it? + response.andExpect(content().contentType(contentType)); + response.andExpect(jsonPath("$", Matchers.is( + RequestCopyMatcher.matchRequestCopy(request)))); + //} catch (Exception e) { + // System.err.println(e.getMessage()); + //} + // Clean up. + bitstream.setDeleted(true); context.restoreAuthSystemState(); } From 10dde356311c2a935f60eef4e688bb5bc6d2486a Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 1 Oct 2018 16:43:30 -0400 Subject: [PATCH 0132/1254] [DS-3952] Work around NPE problem in RequestItem: ensure that accept_request is set. --- .../src/test/java/org/dspace/builder/RequestItemBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index 5ada709b75..13f64e6312 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -59,7 +59,9 @@ public class RequestItemBuilder } catch (SQLException ex) { return handleException(ex); } - this.requestItem = requestItemService.findByToken(context, token); + requestItem = requestItemService.findByToken(context, token); + requestItem.setAccept_request(false); + requestItemService.update(context, requestItem); return this; } From c063434e010fc4e591c7f68bff03cdeb5a9bb8ea Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 1 Oct 2018 16:45:45 -0400 Subject: [PATCH 0133/1254] [DS-3952] Fix what the tests say was broken. --- .../app/rest/model/RequestItemRest.java | 18 +++---- .../repository/RequestItemRepository.java | 50 +++++++++++++------ .../app/rest/matcher/RequestCopyMatcher.java | 9 ++-- .../repository/RequestItemRepositoryIT.java | 32 +++--------- .../rest/converter/RequestItemConverter.java | 19 ++++++- 5 files changed, 67 insertions(+), 61 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 0d3ad65190..2be98e177a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -10,10 +10,7 @@ package org.dspace.app.rest.model; import java.util.Date; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.dspace.app.rest.converter.BitstreamConverter; -import org.dspace.app.rest.converter.ItemConverter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; +import org.dspace.app.rest.RestResourceController; /** * Represent a user's request for a copy of an Item. @@ -21,19 +18,12 @@ import org.springframework.stereotype.Component; * * @author Mark H. Wood */ -@Component public class RequestItemRest extends BaseObjectRest { public static final String NAME = "copyrequest"; public static final String CATEGORY = RestAddressableModel.TOOLS; - @Autowired(required = true) - private BitstreamConverter bitstreamConverter; - - @Autowired(required = true) - private ItemConverter itemConverter; - protected BitstreamRest bitstream; protected Date decisionDate; protected Date expires; @@ -204,6 +194,10 @@ public class RequestItemRest this.allfiles = allfiles; } + /* + * Common REST object methods. + */ + @Override public String getCategory() { return CATEGORY; @@ -211,7 +205,7 @@ public class RequestItemRest @Override public Class getController() { - throw new UnsupportedOperationException("Not supported yet."); + return RestResourceController.class; } @Override 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 7067fe30d6..38460d3d91 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 @@ -15,6 +15,8 @@ import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; @@ -31,6 +33,7 @@ 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.jdbc.UncategorizedSQLException; import org.springframework.stereotype.Component; /** @@ -41,6 +44,7 @@ import org.springframework.stereotype.Component; @Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) public class RequestItemRepository extends DSpaceRestRepository { + private static final Logger LOG = LogManager.getLogger(); @Autowired(required = true) protected RequestItemService requestItemService; @@ -56,7 +60,11 @@ public class RequestItemRepository @Override public RequestItemRest findOne(Context context, String id) { RequestItem requestItem = requestItemService.findByToken(context, id); - return requestItemConverter.convert(requestItem, new DefaultProjection()); + if (null == requestItem) { + return null; + } else { + return requestItemConverter.convert(requestItem, new DefaultProjection()); + } } @Override @@ -66,8 +74,8 @@ public class RequestItemRepository } @Override - protected RequestItemRest createAndReturn(Context context) - throws SQLException { + protected RequestItemRest createAndReturn(Context context) { + // Map the user's request to a REST object. HttpServletRequest httpRequest = getRequestService().getCurrentRequest() .getHttpServletRequest(); ObjectMapper mapper = new ObjectMapper(); @@ -75,19 +83,31 @@ public class RequestItemRepository try { ServletInputStream input = httpRequest.getInputStream(); requestItemRest = mapper.readValue(input, RequestItemRest.class); - } catch (IOException e1) { - throw new UnprocessableEntityException("Error parsing request body", e1); + } catch (IOException ex) { + throw new UnprocessableEntityException("Error parsing request body", ex); } - Bitstream bitstream = bitstreamService.find(context, - UUID.fromString(requestItemRest.getBitstream().getUuid())); - Item item = itemService.find(context, - UUID.fromString(requestItemRest.getItem().getUuid())); - String itemRequestId = requestItemService.createRequest(context, - bitstream, item, - requestItemRest.isAllfiles(), - requestItemRest.getReqEmail(), - requestItemRest.getReqName(), - requestItemRest.getReqMessage()); + + // Create the DSpace item request object. + Bitstream bitstream; + Item item; + String itemRequestId; + try { + bitstream = bitstreamService.find(context, + UUID.fromString(requestItemRest.getBitstream().getUuid())); + item = itemService.find(context, + UUID.fromString(requestItemRest.getItem().getUuid())); + itemRequestId = requestItemService.createRequest(context, + bitstream, item, + requestItemRest.isAllfiles(), + requestItemRest.getReqEmail(), + requestItemRest.getReqName(), + requestItemRest.getReqMessage()); + } catch (SQLException ex) { + LOG.error("New RequestItem not saved.", ex); + throw new UncategorizedSQLException("New RequestItem save", null, ex); + } + + // Link the REST object to the model object, and return it. requestItemRest.setToken(itemRequestId); return requestItemRest; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java index b6f151b075..3ac60ec5ec 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -27,16 +27,13 @@ public class RequestCopyMatcher { public static Matcher matchRequestCopy(RequestItem request) { return allOf( - hasJsonPath("$.bitstream", Matchers.not(Matchers.empty())), - hasJsonPath("$.item", Matchers.not(Matchers.empty())), - hasJsonPath("$.allFiles", is(String.valueOf(request.isAllfiles()))), + hasJsonPath("$._links.bitstream", Matchers.not(Matchers.empty())), + hasJsonPath("$._links.item", Matchers.not(Matchers.empty())), + hasJsonPath("$.allfiles", is(request.isAllfiles())), hasJsonPath("$.reqEmail", is(request.getReqEmail())), hasJsonPath("$.reqName", is(request.getReqName())), hasJsonPath("$.reqMessage", is(request.getReqMessage())), hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$._embedded.schema", Matchers.not(Matchers.empty())), - hasJsonPath("$._links.schema.href", - Matchers.containsString(RequestItemRepositoryIT.URI_ROOT)), hasJsonPath("$._links.self.href", Matchers.containsString(RequestItemRepositoryIT.URI_ROOT)) ); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java index 418cd21602..e49a1e034c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.repository; +import static org.junit.Assert.assertEquals; 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; @@ -23,6 +24,7 @@ import org.dspace.builder.ItemBuilder; import org.dspace.builder.RequestItemBuilder; import org.dspace.app.rest.matcher.RequestCopyMatcher; import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.repository.RequestItemRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.content.Bitstream; import org.dspace.content.Collection; @@ -30,9 +32,6 @@ import org.dspace.content.Community; import org.dspace.content.Item; import org.hamcrest.Matchers; import org.junit.Test; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; /** * @@ -41,7 +40,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde public class RequestItemRepositoryIT extends AbstractControllerIntegrationTest { /** Where to find {@link RequestItem}s in the local URL namespace. */ - public static final String URI_ROOT = "/api/" + public static final String URI_ROOT = REST_SERVER_URL + RequestItemRest.CATEGORY + '/' + RequestItemRest.NAME + 's'; @@ -99,24 +98,11 @@ public class RequestItemRepositoryIT // Test: was it created correctly? final String uri = URI_ROOT + '/' + request.getToken(); -/* getClient().perform(get(uri)) .andExpect(status().isOk()) // Can we find it? .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( RequestCopyMatcher.matchRequestCopy(request)))); -*/ - //try { - MockMvc client = getClient(); - MockHttpServletRequestBuilder get = get(uri); - ResultActions response = client.perform(get); - response.andExpect(status().isOk()); // Can we find it? - response.andExpect(content().contentType(contentType)); - response.andExpect(jsonPath("$", Matchers.is( - RequestCopyMatcher.matchRequestCopy(request)))); - //} catch (Exception e) { - // System.err.println(e.getMessage()); - //} // Clean up. bitstream.setDeleted(true); @@ -182,19 +168,13 @@ public class RequestItemRepositoryIT /** * Test of getDomainClass method, of class RequestItemRepository. */ -/* @Test - public void testGetDomainClass() - { + public void testGetDomainClass() { System.out.println("getDomainClass"); RequestItemRepository instance = new RequestItemRepository(); - Class expResult = null; - Class result = instance.getDomainClass(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); + Class instanceClass = instance.getDomainClass(); + assertEquals("Wrong domain class", instanceClass, RequestItemRest.class); } -*/ /** * Test of wrapResource method, of class RequestItemRepository. diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java index f999ad34e1..319f7cc4a1 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.converter; import java.sql.SQLException; +import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; @@ -16,6 +17,10 @@ import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.model.RequestItemRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.RequestService; import org.slf4j.Logger; @@ -46,6 +51,12 @@ public class RequestItemConverter @Autowired(required = true) protected RequestService requestService; + @Autowired(required = true) + protected BitstreamService bitstreamService; + + @Autowired(required = true) + protected ItemService itemService; + @Override public RequestItemRest fromModel(RequestItem requestItem) { RequestItemRest requestItemRest = new RequestItemRest(); @@ -73,9 +84,13 @@ public class RequestItemConverter String token = obj.getToken(); if (StringUtils.isBlank(token)) { // No token, so create a new request. try { + Bitstream bitstream = bitstreamService.find(context, + UUID.fromString(obj.getBitstream().getUuid())); + Item item = itemService.find(context, + UUID.fromString(obj.getItem().getUuid())); token = requestItemService.createRequest(context, - obj.getBitstream(), - obj.getItem(), + bitstream, + item, obj.isAllfiles(), obj.getReqEmail(), obj.getReqName(), From 6fa5bf6548fbbcca369e874a001db5b49c21f65d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 2 Oct 2018 12:33:53 -0400 Subject: [PATCH 0134/1254] [DS-3952] Drop dangling references that caused unfortunate coupling with following test classes. --- .../test/java/org/dspace/builder/RequestItemBuilder.java | 9 +++++++-- .../java/org/dspace/builder/RequestItemHelperDAO.java | 4 ++-- .../org/dspace/app/rest/matcher/RequestCopyMatcher.java | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index 13f64e6312..b04ec643c0 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -41,8 +41,13 @@ public class RequestItemBuilder @Override public void cleanup() throws Exception { - LOG.info("cleanup()"); - delete(context,requestItem); + LOG.debug("cleanup()"); + if (null != requestItem) { + delete(context, requestItem); + requestItem = null; + } else { + LOG.debug("nothing to clean up."); + } } public static RequestItemBuilder createRequestItem(Context ctx, Item item, diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java index d6128ca15a..efaa83a3b3 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java @@ -27,7 +27,7 @@ public class RequestItemHelperDAO extends AbstractHibernateDAO { void delete(Context context, String token) throws SQLException { - LOG.info("delete request with token {}", token); + LOG.debug("delete request with token {}", token); Query delete = createQuery(context, "DELETE FROM " + RequestItem.class.getSimpleName() @@ -35,6 +35,6 @@ public class RequestItemHelperDAO extends AbstractHibernateDAO { delete.setParameter("token", token); int howmany = delete.executeUpdate(); - LOG.info("Deleted {} requests", howmany); + LOG.debug("Deleted {} requests", howmany); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java index 3ac60ec5ec..af18555df6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -19,6 +19,7 @@ import org.hamcrest.Matcher; import org.hamcrest.Matchers; /** + * Compare {@link RequestItem}s. * * @author Mark H. Wood */ From 09b066ca9171272dba201829d0c8ef15530d0684 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 8 Nov 2018 08:55:24 -0500 Subject: [PATCH 0135/1254] [DS-3952] Move request deletion from test to main. --- .../app/requestitem/RequestItemServiceImpl.java | 11 ++++++++++- .../app/requestitem/dao/RequestItemDAO.java | 16 ++++++++++++---- .../requestitem/dao/impl/RequestItemDAOImpl.java | 4 +--- .../requestitem/service/RequestItemService.java | 12 +++++++++--- .../org/dspace/builder/RequestItemBuilder.java | 2 +- .../org/dspace/builder/RequestItemHelperDAO.java | 2 +- 6 files changed, 34 insertions(+), 13 deletions(-) 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 7f3d086c03..451af9bc1b 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 @@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** * Service implementation for the RequestItem object. - * This class is responsible for all business logic calls for the RequestItem object and is autowired by spring. + * This class is responsible for all business logic calls for the RequestItem object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -78,4 +78,13 @@ public class RequestItemServiceImpl implements RequestItemService { log.error(e.getMessage()); } } + + @Override + public void delete(Context context, RequestItem requestItem) { + try { + requestItemDAO.delete(context, requestItem); + } catch (SQLException e) { + log.error(e.getMessage()); + } + } } 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 74caa16d0e..4a4ea6cd90 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 @@ -15,13 +15,21 @@ import org.dspace.core.GenericDAO; /** * Database Access Object interface class for the RequestItem object. - * The implementation of this class is responsible for all database calls for the RequestItem 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 + * The implementation of this class is responsible for all database calls for + * the RequestItem 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 kevinvandevelde at atmire.com */ public interface RequestItemDAO extends GenericDAO { - + /** + * Fetch a request named by its unique token (passed in emails). + * + * @param context the current DSpace context. + * @param token uniquely identifies the request. + * @return the found request (or {@code null}?) + * @throws SQLException passed through. + */ public RequestItem findByToken(Context context, String token) 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 351f40ae13..6830a5df9a 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 @@ -20,7 +20,7 @@ import org.dspace.core.Context; /** * Hibernate implementation of the Database Access Object interface class for the RequestItem object. - * This class is responsible for all database calls for the RequestItem object and is autowired by spring + * This class is responsible for all database calls for the RequestItem object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -39,6 +39,4 @@ public class RequestItemDAOImpl extends AbstractHibernateDAO implem criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.token), token)); return uniqueResult(context, criteriaQuery, false, RequestItem.class, -1, -1); } - - } 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 43e1a0201a..c7f5926463 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 @@ -16,8 +16,8 @@ import org.dspace.core.Context; /** * Service interface class for the RequestItem object. - * The implementation of this class is responsible for all business logic calls for the RequestItem object and is - * autowired by spring + * The implementation of this class is responsible for all business logic calls + * for the RequestItem object and is autowired by Spring. * * @author kevinvandevelde at atmire.com */ @@ -51,5 +51,11 @@ public interface RequestItemService { */ public void update(Context context, RequestItem requestItem); - + /** + * Remove the record from the database. + * + * @param context current DSpace context. + * @param request record to be removed. + */ + public void delete(Context context, RequestItem request); } diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index b04ec643c0..23fdb66cc3 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -79,7 +79,7 @@ public class RequestItemBuilder @Override public void delete(Context context, RequestItem request) throws Exception { - new RequestItemHelperDAO().delete(context, request.getToken()); + requestItemService.delete(context, request); } @Override diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java index efaa83a3b3..d86b3d22b5 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java @@ -6,7 +6,7 @@ * http://www.dspace.org/license/ */ -package org.dspace.app.rest.builder; +package org.dspace.builder; import java.sql.SQLException; import javax.persistence.Query; From f80546b6d738f0e22699d4f2400e6284f8124b01 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 8 Nov 2018 15:20:35 -0500 Subject: [PATCH 0136/1254] [DS-3952] Remove unused setup/teardown; more documentation; correct assertion. --- .../repository/RequestItemRepositoryIT.java | 26 +-- .../rest/model/hateoas/DSpaceResource.java | 215 ++++++++++++++++++ 2 files changed, 216 insertions(+), 25 deletions(-) create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java index e49a1e034c..672626d4d7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java @@ -44,30 +44,6 @@ public class RequestItemRepositoryIT + RequestItemRest.CATEGORY + '/' + RequestItemRest.NAME + 's'; -/* - @BeforeClass - public static void setUpClass() { - } -*/ - -/* - @AfterClass - public static void tearDownClass() { - } -*/ - -/* - @Before - public void setUp() { - } -*/ - -/* - @After - public void tearDown() { - } -*/ - /** * Test of findOne method, of class RequestItemRepository. * @throws java.lang.Exception passed through. @@ -173,7 +149,7 @@ public class RequestItemRepositoryIT System.out.println("getDomainClass"); RequestItemRepository instance = new RequestItemRepository(); Class instanceClass = instance.getDomainClass(); - assertEquals("Wrong domain class", instanceClass, RequestItemRest.class); + assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); } /** diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java new file mode 100644 index 0000000000..f72743675b --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java @@ -0,0 +1,215 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.LinksRest; +import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.repository.DSpaceRestRepository; +import org.dspace.app.rest.repository.LinkRestRepository; +import org.dspace.app.rest.utils.Utils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.hateoas.Link; + +/** + * A base class for DSpace Rest HAL Resource. The HAL Resource wraps the REST + * Resource adding support for the links and embedded resources. Each property + * of the wrapped REST resource is automatically translated in a link and the + * available information included as embedded resource + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * @param Holder of a user request for a manipulable object. + */ +public abstract class DSpaceResource extends HALResource { + + /** + * Constructor. + * + * @param data a request for a resource. + * @param utils utility methods. + * @param rels names of desired linked resource types. + */ + public DSpaceResource(T data, Utils utils, String... rels) { + super(data); + + if (data != null) { + try { + LinksRest links = data.getClass().getDeclaredAnnotation(LinksRest.class); + if (links != null && rels != null) { + List relsList = Arrays.asList(rels); + for (LinkRest linkAnnotation : links.links()) { + if (!relsList.contains(linkAnnotation.name())) { + continue; + } + String name = linkAnnotation.name(); + Link linkToSubResource = utils.linkToSubResource(data, name); + String apiCategory = data.getCategory(); + String model = data.getType(); + LinkRestRepository linkRepository = utils + .getLinkResourceRepository(apiCategory, model, linkAnnotation.name()); + + if (!linkRepository.isEmbeddableRelation(data, linkAnnotation.name())) { + continue; + } + try { + Method[] methods = linkRepository.getClass().getMethods(); + boolean found = false; + for (Method m : methods) { + if (StringUtils.equals(m.getName(), linkAnnotation.method())) { + // TODO add support for single linked object other than for collections + Page pageResult = (Page) m + .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, null); + EmbeddedPage ep = new EmbeddedPage(linkToSubResource.getHref(), pageResult, + null, name); + embedded.put(name, ep); + found = true; + } + } + // TODO custom exception + if (!found) { + throw new RuntimeException( + "Method for relation " + linkAnnotation.name() + " not found: " + linkAnnotation + .method()); + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } + + for (PropertyDescriptor pd : Introspector.getBeanInfo(data.getClass()).getPropertyDescriptors()) { + Method readMethod = pd.getReadMethod(); + String name = pd.getName(); + if (readMethod != null && !"class".equals(name)) { + LinkRest linkAnnotation = AnnotationUtils.findAnnotation(readMethod, LinkRest.class); + + if (linkAnnotation != null) { + if (StringUtils.isNotBlank(linkAnnotation.name())) { + name = linkAnnotation.name(); + } + Link linkToSubResource = utils.linkToSubResource(data, name); + // no method is specified to retrieve the linked object(s) so check if it is already here + if (StringUtils.isBlank(linkAnnotation.method())) { + Object linkedObject = readMethod.invoke(data); + Object wrapObject = linkedObject; + if (linkedObject instanceof RestAddressableModel) { + RestAddressableModel linkedRM = (RestAddressableModel) linkedObject; + wrapObject = utils.getResourceRepository(linkedRM.getCategory(), linkedRM.getType()) + .wrapResource(linkedRM); + + } else { + if (linkedObject instanceof List) { + List linkedRMList = (List) + linkedObject; + if (linkedRMList.size() > 0) { + + DSpaceRestRepository resourceRepository = utils + .getResourceRepository(linkedRMList.get(0).getCategory(), + linkedRMList.get(0).getType()); + // TODO should we force pagination also of embedded resource? + // This will force pagination with size 10 for embedded collections as well +// int pageSize = 1; +// PageImpl page = new PageImpl( +// linkedRMList.subList(0, +// linkedRMList.size() > pageSize ? pageSize : linkedRMList.size()), +// new PageRequest(0, pageSize), linkedRMList.size()); + PageImpl page = new PageImpl(linkedRMList); + wrapObject = new EmbeddedPage(linkToSubResource.getHref(), + page.map(resourceRepository::wrapResource), + linkedRMList, name); + } else { + PageImpl page = new PageImpl(linkedRMList); + wrapObject = new EmbeddedPage(linkToSubResource.getHref(), page, + linkedRMList, name); + } + } + } + + embedded.put(name, wrapObject); + } else { + // call the link repository + try { + String apiCategory = data.getCategory(); + String model = data.getType(); + LinkRestRepository linkRepository = utils + .getLinkResourceRepository(apiCategory, model, linkAnnotation.name()); + Method[] methods = linkRepository.getClass().getMethods(); + boolean found = false; + for (Method m : methods) { + if (StringUtils.equals(m.getName(), linkAnnotation.method())) { + if (Page.class.isAssignableFrom(m.getReturnType())) { + Page pageResult = (Page) m + .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, + null); + EmbeddedPage ep = new EmbeddedPage(linkToSubResource.getHref(), + pageResult, null, name); + embedded.put(name, ep); + } else { + RestAddressableModel object = (RestAddressableModel) m + .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, + null); + HALResource ep = linkRepository + .wrapResource(object, linkToSubResource.getHref()); + embedded.put(name, ep); + } + + found = true; + } + } + // TODO custom exception + if (!found) { + throw new RuntimeException("Method for relation " + linkAnnotation + .name() + " not found: " + linkAnnotation.method()); + } + } catch (IllegalAccessException | IllegalArgumentException | + InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } else if (RestAddressableModel.class.isAssignableFrom(readMethod.getReturnType())) { + RestAddressableModel linkedObject = (RestAddressableModel) readMethod.invoke(data); + if (linkedObject != null) { + embedded.put(name, + utils.getResourceRepository(linkedObject.getCategory(), + linkedObject.getType()) + .wrapResource(linkedObject)); + } else { + embedded.put(name, null); + } + } + } + } + } catch (IntrospectionException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } + + //Trick to make Java Understand that our content extends RestModel + @JsonUnwrapped + @Override + public T getContent() { + return super.getContent(); + } +} From 0ccfba6a1aa7c31df45d8b3236b7b206f9427dd8 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 4 Jun 2019 09:49:28 -0400 Subject: [PATCH 0137/1254] [DS-3952] Catch up with switch to Log4J v2 -- replace SLF4J. --- .../test/java/org/dspace/builder/RequestItemBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index 23fdb66cc3..bc57073e70 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -10,13 +10,13 @@ package org.dspace.builder; import java.sql.SQLException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Manage the creation and cleanup of {@link RequestItem}s for testing. @@ -25,7 +25,7 @@ import org.slf4j.LoggerFactory; */ public class RequestItemBuilder extends AbstractBuilder { - private static final Logger LOG = LoggerFactory.getLogger(RequestItemBuilder.class); + private static final Logger LOG = LogManager.getLogger(); public static final String REQ_EMAIL = "jsmith@example.com"; public static final String REQ_NAME = "John Smith"; From ad4610d49fb7b8a618c2f7623980a80e40919890 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 4 Jun 2019 09:50:23 -0400 Subject: [PATCH 0138/1254] [DS-3952] Add RequestItemBuilder to the clean-up sequence. --- .../org/dspace/builder/util/AbstractBuilderCleanupUtil.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index 82555c2d37..ac7cc025b2 100644 --- a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -29,6 +29,7 @@ import org.dspace.builder.PoolTaskBuilder; import org.dspace.builder.ProcessBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.RequestItemBuilder; import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.builder.SiteBuilder; import org.dspace.builder.WorkflowItemBuilder; @@ -37,7 +38,7 @@ import org.dspace.builder.WorkspaceItemBuilder; /** * This class will ensure that all the builders that are registered will be cleaned up in the order as defined * in the constructor. This will ensure foreign-key constraint safe deletion of the objects made with these - * builders + * builders. */ public class AbstractBuilderCleanupUtil { @@ -70,6 +71,7 @@ public class AbstractBuilderCleanupUtil { map.put(EPersonBuilder.class.getName(), new LinkedList<>()); map.put(GroupBuilder.class.getName(), new LinkedList<>()); map.put(BundleBuilder.class.getName(), new LinkedList<>()); + map.put(RequestItemBuilder.class.getName(), new LinkedList<>()); map.put(ItemBuilder.class.getName(), new LinkedList<>()); map.put(MetadataFieldBuilder.class.getName(), new LinkedList<>()); map.put(MetadataSchemaBuilder.class.getName(), new LinkedList<>()); From 2fae54bfacf21249cb70a52881b5838910eb0a53 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 4 Jun 2019 10:03:46 -0400 Subject: [PATCH 0139/1254] [DS-3952] More SLF4J -> Log4Jv2 catch-up. --- .../repository/RequestItemRepository.java | 1 + .../rest/converter/RequestItemConverter.java | 108 --------- .../rest/model/hateoas/DSpaceResource.java | 215 ------------------ 3 files changed, 1 insertion(+), 323 deletions(-) delete mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java delete mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java 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 38460d3d91..03d19450e2 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 @@ -45,6 +45,7 @@ import org.springframework.stereotype.Component; public class RequestItemRepository extends DSpaceRestRepository { private static final Logger LOG = LogManager.getLogger(); + @Autowired(required = true) protected RequestItemService requestItemService; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java deleted file mode 100644 index 319f7cc4a1..0000000000 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.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.converter; - -import java.sql.SQLException; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.requestitem.RequestItem; -import org.dspace.app.requestitem.service.RequestItemService; -import org.dspace.app.rest.model.RequestItemRest; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.content.Bitstream; -import org.dspace.content.Item; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.services.RequestService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * Convert between {@link org.dspace.app.requestitem.RequestItem} and - * {@link org.dspace.app.rest.model.RequestItemRest}. - * - * @author Mark H. Wood - */ -@Component -public class RequestItemConverter - extends DSpaceConverter { - private static final Logger LOG = LoggerFactory.getLogger(RequestItemConverter.class); - - @Autowired(required = true) - protected BitstreamConverter bitstreamConverter; - - @Autowired(required = true) - protected ItemConverter itemConverter; - - @Autowired(required = true) - protected RequestItemService requestItemService; - - @Autowired(required = true) - protected RequestService requestService; - - @Autowired(required = true) - protected BitstreamService bitstreamService; - - @Autowired(required = true) - protected ItemService itemService; - - @Override - public RequestItemRest fromModel(RequestItem requestItem) { - RequestItemRest requestItemRest = new RequestItemRest(); - requestItemRest.setAcceptRequest(requestItem.isAccept_request()); - requestItemRest.setAllfiles(requestItem.isAllfiles()); - requestItemRest.setBitstream(bitstreamConverter.fromModel(requestItem.getBitstream())); - requestItemRest.setDecisionDate(requestItem.getDecision_date()); - requestItemRest.setExpires(requestItem.getExpires()); - requestItemRest.setId(requestItem.getID()); - requestItemRest.setItem(itemConverter.fromModel(requestItem.getItem())); - requestItemRest.setReqEmail(requestItem.getReqEmail()); - requestItemRest.setReqMessage(requestItem.getReqMessage()); - requestItemRest.setReqName(requestItem.getReqName()); - requestItemRest.setRequestDate(requestItem.getRequest_date()); - requestItemRest.setToken(requestItem.getToken()); - return requestItemRest; - } - - @Override - public RequestItem toModel(RequestItemRest obj) { - HttpServletRequest request = requestService.getCurrentRequest().getHttpServletRequest(); - Context context = ContextUtil.obtainContext(request); - - // Did we receive a token? That is: are we updating an existing request? - String token = obj.getToken(); - if (StringUtils.isBlank(token)) { // No token, so create a new request. - try { - Bitstream bitstream = bitstreamService.find(context, - UUID.fromString(obj.getBitstream().getUuid())); - Item item = itemService.find(context, - UUID.fromString(obj.getItem().getUuid())); - token = requestItemService.createRequest(context, - bitstream, - item, - obj.isAllfiles(), - obj.getReqEmail(), - obj.getReqName(), - obj.getReqMessage()); - } catch (SQLException ex) { - LOG.error(ex.getMessage(), ex); - } - } // Otherwise (we have a token) this is updating an existing request. - - RequestItem requestItem = requestItemService.findByToken(context, token); - requestItem.setAccept_request(obj.isAcceptRequest()); - requestItem.setDecision_date(obj.getDecisionDate()); - return requestItem; - } -} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java deleted file mode 100644 index f72743675b..0000000000 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java +++ /dev/null @@ -1,215 +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.hateoas; - -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.model.BaseObjectRest; -import org.dspace.app.rest.model.LinkRest; -import org.dspace.app.rest.model.LinksRest; -import org.dspace.app.rest.model.RestAddressableModel; -import org.dspace.app.rest.repository.DSpaceRestRepository; -import org.dspace.app.rest.repository.LinkRestRepository; -import org.dspace.app.rest.utils.Utils; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.hateoas.Link; - -/** - * A base class for DSpace Rest HAL Resource. The HAL Resource wraps the REST - * Resource adding support for the links and embedded resources. Each property - * of the wrapped REST resource is automatically translated in a link and the - * available information included as embedded resource - * - * @author Andrea Bollini (andrea.bollini at 4science.it) - * @param Holder of a user request for a manipulable object. - */ -public abstract class DSpaceResource extends HALResource { - - /** - * Constructor. - * - * @param data a request for a resource. - * @param utils utility methods. - * @param rels names of desired linked resource types. - */ - public DSpaceResource(T data, Utils utils, String... rels) { - super(data); - - if (data != null) { - try { - LinksRest links = data.getClass().getDeclaredAnnotation(LinksRest.class); - if (links != null && rels != null) { - List relsList = Arrays.asList(rels); - for (LinkRest linkAnnotation : links.links()) { - if (!relsList.contains(linkAnnotation.name())) { - continue; - } - String name = linkAnnotation.name(); - Link linkToSubResource = utils.linkToSubResource(data, name); - String apiCategory = data.getCategory(); - String model = data.getType(); - LinkRestRepository linkRepository = utils - .getLinkResourceRepository(apiCategory, model, linkAnnotation.name()); - - if (!linkRepository.isEmbeddableRelation(data, linkAnnotation.name())) { - continue; - } - try { - Method[] methods = linkRepository.getClass().getMethods(); - boolean found = false; - for (Method m : methods) { - if (StringUtils.equals(m.getName(), linkAnnotation.method())) { - // TODO add support for single linked object other than for collections - Page pageResult = (Page) m - .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, null); - EmbeddedPage ep = new EmbeddedPage(linkToSubResource.getHref(), pageResult, - null, name); - embedded.put(name, ep); - found = true; - } - } - // TODO custom exception - if (!found) { - throw new RuntimeException( - "Method for relation " + linkAnnotation.name() + " not found: " + linkAnnotation - .method()); - } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - } - - for (PropertyDescriptor pd : Introspector.getBeanInfo(data.getClass()).getPropertyDescriptors()) { - Method readMethod = pd.getReadMethod(); - String name = pd.getName(); - if (readMethod != null && !"class".equals(name)) { - LinkRest linkAnnotation = AnnotationUtils.findAnnotation(readMethod, LinkRest.class); - - if (linkAnnotation != null) { - if (StringUtils.isNotBlank(linkAnnotation.name())) { - name = linkAnnotation.name(); - } - Link linkToSubResource = utils.linkToSubResource(data, name); - // no method is specified to retrieve the linked object(s) so check if it is already here - if (StringUtils.isBlank(linkAnnotation.method())) { - Object linkedObject = readMethod.invoke(data); - Object wrapObject = linkedObject; - if (linkedObject instanceof RestAddressableModel) { - RestAddressableModel linkedRM = (RestAddressableModel) linkedObject; - wrapObject = utils.getResourceRepository(linkedRM.getCategory(), linkedRM.getType()) - .wrapResource(linkedRM); - - } else { - if (linkedObject instanceof List) { - List linkedRMList = (List) - linkedObject; - if (linkedRMList.size() > 0) { - - DSpaceRestRepository resourceRepository = utils - .getResourceRepository(linkedRMList.get(0).getCategory(), - linkedRMList.get(0).getType()); - // TODO should we force pagination also of embedded resource? - // This will force pagination with size 10 for embedded collections as well -// int pageSize = 1; -// PageImpl page = new PageImpl( -// linkedRMList.subList(0, -// linkedRMList.size() > pageSize ? pageSize : linkedRMList.size()), -// new PageRequest(0, pageSize), linkedRMList.size()); - PageImpl page = new PageImpl(linkedRMList); - wrapObject = new EmbeddedPage(linkToSubResource.getHref(), - page.map(resourceRepository::wrapResource), - linkedRMList, name); - } else { - PageImpl page = new PageImpl(linkedRMList); - wrapObject = new EmbeddedPage(linkToSubResource.getHref(), page, - linkedRMList, name); - } - } - } - - embedded.put(name, wrapObject); - } else { - // call the link repository - try { - String apiCategory = data.getCategory(); - String model = data.getType(); - LinkRestRepository linkRepository = utils - .getLinkResourceRepository(apiCategory, model, linkAnnotation.name()); - Method[] methods = linkRepository.getClass().getMethods(); - boolean found = false; - for (Method m : methods) { - if (StringUtils.equals(m.getName(), linkAnnotation.method())) { - if (Page.class.isAssignableFrom(m.getReturnType())) { - Page pageResult = (Page) m - .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, - null); - EmbeddedPage ep = new EmbeddedPage(linkToSubResource.getHref(), - pageResult, null, name); - embedded.put(name, ep); - } else { - RestAddressableModel object = (RestAddressableModel) m - .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, - null); - HALResource ep = linkRepository - .wrapResource(object, linkToSubResource.getHref()); - embedded.put(name, ep); - } - - found = true; - } - } - // TODO custom exception - if (!found) { - throw new RuntimeException("Method for relation " + linkAnnotation - .name() + " not found: " + linkAnnotation.method()); - } - } catch (IllegalAccessException | IllegalArgumentException | - InvocationTargetException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - } else if (RestAddressableModel.class.isAssignableFrom(readMethod.getReturnType())) { - RestAddressableModel linkedObject = (RestAddressableModel) readMethod.invoke(data); - if (linkedObject != null) { - embedded.put(name, - utils.getResourceRepository(linkedObject.getCategory(), - linkedObject.getType()) - .wrapResource(linkedObject)); - } else { - embedded.put(name, null); - } - } - } - } - } catch (IntrospectionException | IllegalArgumentException | IllegalAccessException - | InvocationTargetException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - } - - //Trick to make Java Understand that our content extends RestModel - @JsonUnwrapped - @Override - public T getContent() { - return super.getContent(); - } -} From 1e194db05bbd3727b0f5d4743d3ca42274e145eb Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 4 Jun 2019 10:50:19 -0400 Subject: [PATCH 0140/1254] [DS-3952] Correct builder cleanup priority; remove remnant of old priority system. --- .../java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index ac7cc025b2..6cedf885ed 100644 --- a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -66,6 +66,7 @@ public class AbstractBuilderCleanupUtil { map.put(BitstreamBuilder.class.getName(), new LinkedList<>()); map.put(BitstreamFormatBuilder.class.getName(), new LinkedList<>()); map.put(ClaimedTaskBuilder.class.getName(), new LinkedList<>()); + map.put(RequestItemBuilder.class.getName(), new LinkedList<>()); map.put(CollectionBuilder.class.getName(), new LinkedList<>()); map.put(CommunityBuilder.class.getName(), new LinkedList<>()); map.put(EPersonBuilder.class.getName(), new LinkedList<>()); From 670e4532868ef4324adc3175affbc61cb89ffba7 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 5 Jun 2019 11:31:31 -0400 Subject: [PATCH 0141/1254] [DS-3952] Move some tests from integration to unit testing. --- .../app/rest/RequestItemRepositoryTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java new file mode 100644 index 0000000000..2b9b9d5379 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Indiana University. All rights reserved. + * + * Mark H. Wood, IUPUI University Library, Jun 5, 2019 + */ + +/* + * Copyright 2019 Indiana University. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package org.dspace.app.rest; + +import static org.junit.Assert.assertEquals; + +import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.repository.RequestItemRepository; +import org.junit.Test; + +/** + * + * @author Mark H. Wood + */ +public class RequestItemRepositoryTest +{ + /** + * Test of getDomainClass method, of class RequestItemRepository. + */ + @Test + public void testGetDomainClass() + { + System.out.println("getDomainClass"); + RequestItemRepository instance = new RequestItemRepository(); + Class instanceClass = instance.getDomainClass(); + assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); + } + + /** + * Test of wrapResource method, of class RequestItemRepository. + */ + /* + @Test + public void testWrapResource() + { + System.out.println("wrapResource"); + RequestItemRest model = null; + String[] rels = null; + RequestItemRepository instance = new RequestItemRepository(); + RequestItemResource expResult = null; + RequestItemResource result = instance.wrapResource(model, rels); + assertEquals(expResult, result); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + */ + +} From 5f5bc3641e5b4b4f8effd31040a9a49eb7a57d73 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 5 Jun 2019 11:35:28 -0400 Subject: [PATCH 0142/1254] [DS-3952] Align property names with contract; implement POST rather than PUT. --- .../rest/converter/RequestItemConverter.java | 12 +- .../app/rest/model/RequestItemRest.java | 48 +++--- .../repository/RequestItemRepository.java | 50 +++--- .../repository/RequestItemRepositoryIT.java | 160 +++++++++++------- 4 files changed, 153 insertions(+), 117 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java index d2c5cc547b..e6131b4a36 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -45,16 +45,14 @@ public class RequestItemConverter requestItemRest.setAcceptRequest(requestItem.isAccept_request()); requestItemRest.setAllfiles(requestItem.isAllfiles()); - requestItemRest.setBitstream( - bitstreamConverter.convert(requestItem.getBitstream(), projection)); + requestItemRest.setBitstreamId(requestItem.getBitstream().getID().toString()); requestItemRest.setDecisionDate(requestItem.getDecision_date()); requestItemRest.setExpires(requestItem.getExpires()); requestItemRest.setId(requestItem.getID()); - requestItemRest.setItem( - itemConverter.convert(requestItem.getItem(), projection)); - requestItemRest.setReqEmail(requestItem.getReqEmail()); - requestItemRest.setReqMessage(requestItem.getReqMessage()); - requestItemRest.setReqName(requestItem.getReqName()); + requestItemRest.setItemId(requestItem.getItem().getID().toString()); + requestItemRest.setRequestEmail(requestItem.getReqEmail()); + requestItemRest.setRequestMessage(requestItem.getReqMessage()); + requestItemRest.setRequestName(requestItem.getReqName()); requestItemRest.setRequestDate(requestItem.getRequest_date()); requestItemRest.setToken(requestItem.getToken()); return requestItemRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 2be98e177a..401e835fe2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -9,7 +9,8 @@ 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.app.rest.RestResourceController; /** @@ -18,16 +19,20 @@ import org.dspace.app.rest.RestResourceController; * * @author Mark H. Wood */ +@LinksRest(links = { + @LinkRest(name = "bitstream", method = "getUuid"), + @LinkRest(name = "item", method = "getUuid") +}) public class RequestItemRest extends BaseObjectRest { public static final String NAME = "copyrequest"; public static final String CATEGORY = RestAddressableModel.TOOLS; - protected BitstreamRest bitstream; + protected String bitstream_id; protected Date decisionDate; protected Date expires; - protected ItemRest item; + protected String item_id; protected String reqEmail; protected String reqMessage; protected String reqName; @@ -39,17 +44,15 @@ public class RequestItemRest /** * @return the bitstream requested. */ - @LinkRest - @JsonIgnore - public BitstreamRest getBitstream() { - return bitstream; + public String getBitstreamId() { + return bitstream_id; } /** - * @param bitstream the bitstream requested. + * @param bitstream_id the bitstream requested. */ - public void setBitstream(BitstreamRest bitstream) { - this.bitstream = bitstream; + public void setBitstreamId(String bitstream_id) { + this.bitstream_id = bitstream_id; } /** @@ -83,58 +86,56 @@ public class RequestItemRest /** * @return the item requested. */ - @LinkRest - @JsonIgnore - public ItemRest getItem() { - return item; + public String getItemId() { + return item_id; } /** - * @param item the item requested. + * @param item_id the item requested. */ - public void setItem(ItemRest item) { - this.item = item; + public void setItemId(String item_id) { + this.item_id = item_id; } /** * @return the email address of the requester. */ - public String getReqEmail() { + public String getRequestEmail() { return reqEmail; } /** * @param email the email address of the requester. */ - public void setReqEmail(String email) { + public void setRequestEmail(String email) { this.reqEmail = email; } /** * @return the requester's message. */ - public String getReqMessage() { + public String getRequestMessage() { return reqMessage; } /** * @param message the requester's message. */ - public void setReqMessage(String message) { + public void setRequestMessage(String message) { this.reqMessage = message; } /** * @return the requester's name. */ - public String getReqName() { + public String getRequestName() { return reqName; } /** * @param name the requester's name. */ - public void setReqName(String name) { + public void setRequestName(String name) { this.reqName = name; } @@ -209,6 +210,7 @@ public class RequestItemRest } @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getType() { return NAME; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 03d19450e2..20d26b48de 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 @@ -33,7 +33,6 @@ 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.jdbc.UncategorizedSQLException; import org.springframework.stereotype.Component; /** @@ -75,42 +74,39 @@ public class RequestItemRepository } @Override - protected RequestItemRest createAndReturn(Context context) { - // Map the user's request to a REST object. - HttpServletRequest httpRequest = getRequestService().getCurrentRequest() + public RequestItemRest createAndReturn(Context ctx) { + // Fill a RequestItemRest from the client's HTTP request. + HttpServletRequest req = getRequestService() + .getCurrentRequest() .getHttpServletRequest(); ObjectMapper mapper = new ObjectMapper(); - RequestItemRest requestItemRest = null; + RequestItemRest rir; try { - ServletInputStream input = httpRequest.getInputStream(); - requestItemRest = mapper.readValue(input, RequestItemRest.class); + rir = mapper.readValue(req.getInputStream(), RequestItemRest.class); } catch (IOException ex) { - throw new UnprocessableEntityException("Error parsing request body", ex); + throw new UnprocessableEntityException("error parsing the body", ex); } - // Create the DSpace item request object. - Bitstream bitstream; - Item item; - String itemRequestId; + // Create the item request model object from the REST object. + String token; try { - bitstream = bitstreamService.find(context, - UUID.fromString(requestItemRest.getBitstream().getUuid())); - item = itemService.find(context, - UUID.fromString(requestItemRest.getItem().getUuid())); - itemRequestId = requestItemService.createRequest(context, - bitstream, item, - requestItemRest.isAllfiles(), - requestItemRest.getReqEmail(), - requestItemRest.getReqName(), - requestItemRest.getReqMessage()); + Bitstream bitstream = bitstreamService.find(ctx, + UUID.fromString(rir.getBitstreamId())); + Item item = itemService.find(ctx, + UUID.fromString(rir.getItemId())); + token = requestItemService.createRequest(ctx, bitstream, item, + rir.isAllfiles(), rir.getRequestEmail(), rir.getRequestName(), + rir.getRequestMessage()); } catch (SQLException ex) { - LOG.error("New RequestItem not saved.", ex); - throw new UncategorizedSQLException("New RequestItem save", null, ex); + throw new RuntimeException("Item request not created.", ex); } - // Link the REST object to the model object, and return it. - requestItemRest.setToken(itemRequestId); - return requestItemRest; + + // Some fields are given values during creation, so return created request. + RequestItem ri = requestItemService.findByToken(ctx, token); + ri.setAccept_request(false); // Not accepted yet. Must set: DS-4032 + requestItemService.update(ctx, ri); + return requestItemConverter.convert(ri, new DefaultProjection()); } // NOTICE: there is no service method for this -- requests are never deleted? diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java index 672626d4d7..c7d3de8039 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java @@ -7,31 +7,43 @@ */ package org.dspace.app.rest.repository; -import static org.junit.Assert.assertEquals; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; +import java.sql.SQLException; +import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.requestitem.RequestItem; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.RequestItemBuilder; +import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.app.rest.converter.RequestItemConverter; import org.dspace.app.rest.matcher.RequestCopyMatcher; import org.dspace.app.rest.model.RequestItemRest; -import org.dspace.app.rest.repository.RequestItemRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Collection; -import org.dspace.content.Community; import org.dspace.content.Item; import org.hamcrest.Matchers; +import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MvcResult; /** * @@ -44,6 +56,20 @@ public class RequestItemRepositoryIT + RequestItemRest.CATEGORY + '/' + RequestItemRest.NAME + 's'; + @Autowired(required = true) + RequestItemConverter requestItemConverter; + + @Autowired(required = true) + RequestItemService requestItemService; + + @Before + public void init() { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName( + "Parent Community").build(); + context.restoreAuthSystemState(); + } + /** * Test of findOne method, of class RequestItemRepository. * @throws java.lang.Exception passed through. @@ -56,9 +82,7 @@ public class RequestItemRepositoryIT context.turnOffAuthorisationSystem(); // Create necessary supporting objects. - Community community = CommunityBuilder.createCommunity(context) - .build(); - Collection collection = CollectionBuilder.createCollection(context, community) + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) .build(); Item item = ItemBuilder.createItem(context, collection) .build(); @@ -71,7 +95,7 @@ public class RequestItemRepositoryIT .createRequestItem(context, item, bitstream) .build(); - // Test: was it created correctly? + // Test: can we find it? final String uri = URI_ROOT + '/' + request.getToken(); getClient().perform(get(uri)) @@ -105,69 +129,85 @@ public class RequestItemRepositoryIT */ /** - * Test of save method, of class RequestItemRepository. + * Test of createAndReturn method, of class RequestItemRepository. + * + * @throws java.sql.SQLException passed through. + * @throws org.dspace.authorize.AuthorizeException passed through. + * @throws java.io.IOException passed through. */ -/* @Test - public void testSave() - { - System.out.println("save"); - Context context = null; - RequestItemRest ri = null; - RequestItemRepository instance = new RequestItemRepository(); - RequestItemRest expResult = null; - RequestItemRest result = instance.save(context, ri); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); + public void testCreateAndReturn() + throws SQLException, AuthorizeException, IOException, Exception { + System.out.println("testCreateAndReturn"); + + context.turnOffAuthorisationSystem(); + + // Create some necessary objects. + Collection col = CollectionBuilder.createCollection(context, + parentCommunity).build(); + Item item = ItemBuilder.createItem(context, col).build(); + InputStream is = new ByteArrayInputStream(new byte[0]); + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("/dev/null") + .withMimeType("text/plain") + .build(); + + // Fake up a request in REST form. + RequestItemRest rir = new RequestItemRest(); + rir.setBitstreamId(bitstream.getID().toString()); + rir.setItemId(item.getID().toString()); + rir.setRequestEmail(RequestItemBuilder.REQ_EMAIL); + rir.setRequestMessage(RequestItemBuilder.REQ_MESSAGE); + rir.setRequestName(RequestItemBuilder.REQ_NAME); + rir.setAllfiles(false); + + // Create it and see if it was created correctly. + ObjectMapper mapper = new ObjectMapper(); + String authToken = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(isEmptyOrNullString())), + hasJsonPath("$.type", is(RequestItemRest.NAME)), + hasJsonPath("$.token", not(isEmptyOrNullString())), + hasJsonPath("$.request_email", is(RequestItemBuilder.REQ_EMAIL)), + hasJsonPath("$.request_message", is(RequestItemBuilder.REQ_MESSAGE)), + hasJsonPath("$.request_name", is(RequestItemBuilder.REQ_NAME)), + hasJsonPath("$.allfiles", is(false)), + hasJsonPath("$.request_date", not(isEmptyOrNullString())), // TODO should be an ISO datetime + hasJsonPath("$._links.self.href", not(isEmptyOrNullString())) + ))) + .andReturn(); + + // Clean up the created request. + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String requestToken = String.valueOf(map.get("token")); + RequestItem ri = requestItemService.findByToken(context, requestToken); + requestItemService.delete(context, ri); + + context.restoreAuthSystemState(); } -*/ /** * Test of delete method, of class RequestItemRepository. */ -/* + /* @Test public void testDelete() - throws Exception + throws Exception { - System.out.println("delete"); - Context context = null; - String token = ""; - RequestItemRepository instance = new RequestItemRepository(); - instance.delete(context, token); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); + System.out.println("delete"); + Context context = null; + String token = ""; + RequestItemRepository instance = new RequestItemRepository(); + instance.delete(context, token); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); } -*/ - - /** - * Test of getDomainClass method, of class RequestItemRepository. */ - @Test - public void testGetDomainClass() { - System.out.println("getDomainClass"); - RequestItemRepository instance = new RequestItemRepository(); - Class instanceClass = instance.getDomainClass(); - assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); - } - - /** - * Test of wrapResource method, of class RequestItemRepository. - */ -/* - @Test - public void testWrapResource() - { - System.out.println("wrapResource"); - RequestItemRest model = null; - String[] rels = null; - RequestItemRepository instance = new RequestItemRepository(); - RequestItemResource expResult = null; - RequestItemResource result = instance.wrapResource(model, rels); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ } From 546efa1c8b6b1f928b185f0b9d7b9ef402481a24 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 5 Jun 2019 18:57:17 -0400 Subject: [PATCH 0143/1254] [DS-3952] Move test back to integration, tidy, remove commented tests. Making this one quick test into a unit test would take a lot of stubbing and just isn't worth the effort. --- .../repository/RequestItemRepositoryIT.java | 41 ++++--------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java index c7d3de8039..561ed8a825 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java @@ -11,6 +11,7 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -109,25 +110,6 @@ public class RequestItemRepositoryIT context.restoreAuthSystemState(); } - /** - * Test of findAll method, of class RequestItemRepository. - */ -/* - @Test - public void testFindAll() - { - System.out.println("findAll"); - Context context = null; - Pageable pageable = null; - RequestItemRepository instance = new RequestItemRepository(); - Page expResult = null; - Page result = instance.findAll(context, pageable); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - /** * Test of createAndReturn method, of class RequestItemRepository. * @@ -138,7 +120,7 @@ public class RequestItemRepositoryIT @Test public void testCreateAndReturn() throws SQLException, AuthorizeException, IOException, Exception { - System.out.println("testCreateAndReturn"); + System.out.println("createAndReturn"); context.turnOffAuthorisationSystem(); @@ -194,20 +176,13 @@ public class RequestItemRepositoryIT } /** - * Test of delete method, of class RequestItemRepository. + * Test of getDomainClass method, of class RequestItemRepository. */ - /* @Test - public void testDelete() - throws Exception - { - System.out.println("delete"); - Context context = null; - String token = ""; - RequestItemRepository instance = new RequestItemRepository(); - instance.delete(context, token); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); + public void testGetDomainClass() { + System.out.println("getDomainClass"); + RequestItemRepository instance = new RequestItemRepository(); + Class instanceClass = instance.getDomainClass(); + assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); } - */ } From 8a3d23f2dd8a6c550022779a9ef413fe25d93d80 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 5 Jun 2019 20:50:48 -0400 Subject: [PATCH 0144/1254] [DS-3952] More thorough Matcher, and new date matching. --- .../java/org/dspace/matcher/DateMatcher.java | 81 +++++++++++++++++++ .../app/rest/matcher/RequestCopyMatcher.java | 15 ++-- 2 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java diff --git a/dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java new file mode 100644 index 0000000000..f4bd81c993 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java @@ -0,0 +1,81 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +/** + * Hamcrest Matcher for comparing a Date with an ISO 8601 zoned string form + * of a date. + * + * @author Mark H. Wood + */ +public class DateMatcher + extends BaseMatcher { + private static final SimpleDateFormat dateFormat + = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + private final Date matchDate; + + /** + * Create a matcher for a given Date. + * @param matchDate The date that tested values should match. + */ + public DateMatcher(Date matchDate) { + this.matchDate = matchDate; + } + + @Override + public boolean matches(Object testDate) { + // null : null is a match + if (null == matchDate && null == testDate) { + return true; + } + + // Null matchDate never matches non-null testDate + if (null == matchDate) { + return false; + } + + // We only match strings here + if (!(testDate instanceof String)) { + throw new IllegalArgumentException("Argument not a String"); + } + + // Decode the string to a Date + Date testDateDecoded; + try { + testDateDecoded = dateFormat.parse((String)testDate); + } catch (ParseException ex) { + throw new IllegalArgumentException("Argument is not an ISO 8601 zoned date", ex); + } + + // Compare with the Date that must match + return matchDate.equals(testDateDecoded); + } + + @Override + public void describeTo(Description description) { + description.appendText("is the same date as "); + description.appendText(dateFormat.format(matchDate)); + } + + /** + * Return a Matcher for a given Date. + * @param matchDate the date which tested values should match. + * @return a new Matcher for matchDate. + */ + static public DateMatcher dateMatcher(Date matchDate) { + return new DateMatcher(matchDate); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java index af18555df6..80a774f889 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.matcher; 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.is; @@ -28,12 +29,16 @@ public class RequestCopyMatcher { public static Matcher matchRequestCopy(RequestItem request) { return allOf( - hasJsonPath("$._links.bitstream", Matchers.not(Matchers.empty())), - hasJsonPath("$._links.item", Matchers.not(Matchers.empty())), + //hasJsonPath("$._links.bitstream", Matchers.not(Matchers.empty())), + //hasJsonPath("$._links.item", Matchers.not(Matchers.empty())), hasJsonPath("$.allfiles", is(request.isAllfiles())), - hasJsonPath("$.reqEmail", is(request.getReqEmail())), - hasJsonPath("$.reqName", is(request.getReqName())), - hasJsonPath("$.reqMessage", is(request.getReqMessage())), + hasJsonPath("$.request_email", is(request.getReqEmail())), + hasJsonPath("$.request_name", is(request.getReqName())), + hasJsonPath("$.request_message", is(request.getReqMessage())), + hasJsonPath("$.request_date", dateMatcher(request.getRequest_date())), + hasJsonPath("$.accept_request", is(request.isAccept_request())), + hasJsonPath("$.decision_date", dateMatcher(request.getDecision_date())), + hasJsonPath("$.expires", dateMatcher(request.getExpires())), hasJsonPath("$.type", is(RequestItemRest.NAME)), hasJsonPath("$._links.self.href", Matchers.containsString(RequestItemRepositoryIT.URI_ROOT)) From 596df53d0a55930a1bb878b1578504bb39aef462 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Sun, 8 Aug 2021 14:51:22 -0400 Subject: [PATCH 0145/1254] [DS-3952] Satisfy checkstyle and license plugins. --- .../org/dspace/builder/AbstractBuilder.java | 1 - .../app/rest/converter/DSpaceConverter.java | 2 +- .../rest/converter/RequestItemConverter.java | 2 +- .../app/rest/model/RequestItemRest.java | 1 - .../repository/RequestItemRepository.java | 1 - .../app/rest/RequestItemRepositoryTest.java | 24 +++++++------------ .../app/rest/matcher/RequestCopyMatcher.java | 2 +- .../repository/RequestItemRepositoryIT.java | 10 ++++---- 8 files changed, 16 insertions(+), 27 deletions(-) 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 d761bd8b21..b296bd1241 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -8,7 +8,6 @@ package org.dspace.builder; import java.sql.SQLException; -import java.util.LinkedList; import java.util.List; import org.apache.commons.collections4.CollectionUtils; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java index ebfe1f274f..3f95f654fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java @@ -21,7 +21,7 @@ public interface DSpaceConverter { * Convert a DSpace model object into its equivalent REST resource, applying * a given projection. * - * @param modelObject a DSpace API model object. + * @param modelObject a DSpace API model object. * @param projection * @return a resource representing the model object. */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java index e6131b4a36..581e8fd0d0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RequestItemConverter.java @@ -26,7 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired; @Named public class RequestItemConverter implements DSpaceConverter { - @Autowired(required=true) + @Autowired(required = true) protected BitstreamConverter bitstreamConverter; @Autowired(required = true) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 401e835fe2..94cacf6ec4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.model; import java.util.Date; import com.fasterxml.jackson.annotation.JsonProperty; - import org.dspace.app.rest.RestResourceController; /** 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 20d26b48de..7f0eaa24da 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 @@ -11,7 +11,6 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; import java.util.UUID; -import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java index 2b9b9d5379..2962b4d2f9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java @@ -1,13 +1,9 @@ -/* - * Copyright 2019 Indiana University. All rights reserved. +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at * - * Mark H. Wood, IUPUI University Library, Jun 5, 2019 - */ - -/* - * Copyright 2019 Indiana University. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * http://www.dspace.org/license/ */ package org.dspace.app.rest; @@ -22,14 +18,12 @@ import org.junit.Test; * * @author Mark H. Wood */ -public class RequestItemRepositoryTest -{ +public class RequestItemRepositoryTest { /** * Test of getDomainClass method, of class RequestItemRepository. */ @Test - public void testGetDomainClass() - { + public void testGetDomainClass() { System.out.println("getDomainClass"); RequestItemRepository instance = new RequestItemRepository(); Class instanceClass = instance.getDomainClass(); @@ -41,8 +35,7 @@ public class RequestItemRepositoryTest */ /* @Test - public void testWrapResource() - { + public void testWrapResource() { System.out.println("wrapResource"); RequestItemRest model = null; String[] rels = null; @@ -54,5 +47,4 @@ public class RequestItemRepositoryTest fail("The test case is a prototype."); } */ - } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java index 80a774f889..1f2bf58396 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -14,8 +14,8 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; import org.dspace.app.requestitem.RequestItem; -import org.dspace.app.rest.repository.RequestItemRepositoryIT; import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.repository.RequestItemRepositoryIT; import org.hamcrest.Matcher; import org.hamcrest.Matchers; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java index 561ed8a825..95e7f426f1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java @@ -26,17 +26,17 @@ import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.requestitem.RequestItem; -import org.dspace.builder.BitstreamBuilder; -import org.dspace.builder.CollectionBuilder; -import org.dspace.builder.CommunityBuilder; -import org.dspace.builder.ItemBuilder; -import org.dspace.builder.RequestItemBuilder; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; import org.dspace.app.rest.matcher.RequestCopyMatcher; import org.dspace.app.rest.model.RequestItemRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.RequestItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Item; From c427a2e83feffa77d610376bb995106d1c7d37dd Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Sun, 8 Aug 2021 19:12:49 -0400 Subject: [PATCH 0146/1254] [DS-3952] Fix a broken test. Various cleanups. --- .../java/org/dspace/AbstractDSpaceTest.java | 2 +- .../repository/RequestItemRepository.java | 5 +- .../RequestItemRepositoryIT.java | 11 ++-- .../app/rest/RequestItemRepositoryTest.java | 50 ------------------- .../app/rest/matcher/RequestCopyMatcher.java | 2 +- 5 files changed, 9 insertions(+), 61 deletions(-) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{repository => }/RequestItemRepositoryIT.java (93%) delete mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java diff --git a/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java b/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java index e53d6a675d..36477556d3 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractDSpaceTest.java @@ -77,7 +77,7 @@ public class AbstractDSpaceTest { //load the properties of the tests testProps = new Properties(); - URL properties = AbstractUnitTest.class.getClassLoader() + URL properties = AbstractDSpaceTest.class.getClassLoader() .getResource("test-config.properties"); testProps.load(properties.openStream()); 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 7f0eaa24da..22ddf02343 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 @@ -14,8 +14,6 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; @@ -42,8 +40,6 @@ import org.springframework.stereotype.Component; @Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) public class RequestItemRepository extends DSpaceRestRepository { - private static final Logger LOG = LogManager.getLogger(); - @Autowired(required = true) protected RequestItemService requestItemService; @@ -57,6 +53,7 @@ public class RequestItemRepository protected RequestItemConverter requestItemConverter; @Override + //@PreAuthorize(expr) public RequestItemRest findOne(Context context, String id) { RequestItem requestItem = requestItemService.findByToken(context, id); if (null == requestItem) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java similarity index 93% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 95e7f426f1..9a1b7e8261 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.repository; +package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.is; @@ -30,6 +30,7 @@ import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; import org.dspace.app.rest.matcher.RequestCopyMatcher; import org.dspace.app.rest.model.RequestItemRest; +import org.dspace.app.rest.repository.RequestItemRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; import org.dspace.builder.BitstreamBuilder; @@ -156,11 +157,11 @@ public class RequestItemRepositoryIT hasJsonPath("$.id", not(isEmptyOrNullString())), hasJsonPath("$.type", is(RequestItemRest.NAME)), hasJsonPath("$.token", not(isEmptyOrNullString())), - hasJsonPath("$.request_email", is(RequestItemBuilder.REQ_EMAIL)), - hasJsonPath("$.request_message", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.request_name", is(RequestItemBuilder.REQ_NAME)), + hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), + hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), + hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), hasJsonPath("$.allfiles", is(false)), - hasJsonPath("$.request_date", not(isEmptyOrNullString())), // TODO should be an ISO datetime + hasJsonPath("$.requestDate", not(isEmptyOrNullString())), // TODO should be an ISO datetime hasJsonPath("$._links.self.href", not(isEmptyOrNullString())) ))) .andReturn(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java deleted file mode 100644 index 2962b4d2f9..0000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ - -package org.dspace.app.rest; - -import static org.junit.Assert.assertEquals; - -import org.dspace.app.rest.model.RequestItemRest; -import org.dspace.app.rest.repository.RequestItemRepository; -import org.junit.Test; - -/** - * - * @author Mark H. Wood - */ -public class RequestItemRepositoryTest { - /** - * Test of getDomainClass method, of class RequestItemRepository. - */ - @Test - public void testGetDomainClass() { - System.out.println("getDomainClass"); - RequestItemRepository instance = new RequestItemRepository(); - Class instanceClass = instance.getDomainClass(); - assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); - } - - /** - * Test of wrapResource method, of class RequestItemRepository. - */ - /* - @Test - public void testWrapResource() { - System.out.println("wrapResource"); - RequestItemRest model = null; - String[] rels = null; - RequestItemRepository instance = new RequestItemRepository(); - RequestItemResource expResult = null; - RequestItemResource result = instance.wrapResource(model, rels); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } - */ -} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java index 1f2bf58396..aa9931a898 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -15,7 +15,7 @@ import static org.hamcrest.Matchers.is; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.rest.model.RequestItemRest; -import org.dspace.app.rest.repository.RequestItemRepositoryIT; +import org.dspace.app.rest.RequestItemRepositoryIT; import org.hamcrest.Matcher; import org.hamcrest.Matchers; From 8c7ba939135a955d66958c1fc15097eb15c850a0 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 9 Aug 2021 09:26:19 -0400 Subject: [PATCH 0147/1254] [DS-3952] Fix incorrect request parameter names that I missed. --- .../app/rest/matcher/RequestCopyMatcher.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java index aa9931a898..b52a2cb011 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -14,8 +14,8 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; import org.dspace.app.requestitem.RequestItem; -import org.dspace.app.rest.model.RequestItemRest; import org.dspace.app.rest.RequestItemRepositoryIT; +import org.dspace.app.rest.model.RequestItemRest; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -32,12 +32,12 @@ public class RequestCopyMatcher { //hasJsonPath("$._links.bitstream", Matchers.not(Matchers.empty())), //hasJsonPath("$._links.item", Matchers.not(Matchers.empty())), hasJsonPath("$.allfiles", is(request.isAllfiles())), - hasJsonPath("$.request_email", is(request.getReqEmail())), - hasJsonPath("$.request_name", is(request.getReqName())), - hasJsonPath("$.request_message", is(request.getReqMessage())), - hasJsonPath("$.request_date", dateMatcher(request.getRequest_date())), - hasJsonPath("$.accept_request", is(request.isAccept_request())), - hasJsonPath("$.decision_date", dateMatcher(request.getDecision_date())), + hasJsonPath("$.requestEmail", is(request.getReqEmail())), + hasJsonPath("$.requestName", is(request.getReqName())), + hasJsonPath("$.requestMessage", is(request.getReqMessage())), + hasJsonPath("$.requestDate", dateMatcher(request.getRequest_date())), + hasJsonPath("$.acceptRequest", is(request.isAccept_request())), + hasJsonPath("$.decisionDate", dateMatcher(request.getDecision_date())), hasJsonPath("$.expires", dateMatcher(request.getExpires())), hasJsonPath("$.type", is(RequestItemRest.NAME)), hasJsonPath("$._links.self.href", From f435f933ad0b032c61fd063ec9a28afcc27cb559 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 9 Aug 2021 09:27:21 -0400 Subject: [PATCH 0148/1254] [DS-3952] Add auth token to request, and replace some deprecated matchers. --- .../dspace/app/rest/RequestItemRepositoryIT.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 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 9a1b7e8261..e779e41641 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 @@ -9,8 +9,8 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.Matchers.not; +import static org.hamcrest.text.IsEmptyString.emptyOrNullString; import static org.junit.Assert.assertEquals; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -98,9 +98,9 @@ public class RequestItemRepositoryIT .build(); // Test: can we find it? - final String uri = URI_ROOT + '/' - + request.getToken(); - getClient().perform(get(uri)) + String authToken = getAuthToken(admin.getEmail(), password); + final String uri = URI_ROOT + '/' + request.getToken(); + getClient(authToken).perform(get(uri)) .andExpect(status().isOk()) // Can we find it? .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( @@ -154,15 +154,15 @@ public class RequestItemRepositoryIT .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(isEmptyOrNullString())), + hasJsonPath("$.id", not(is(emptyOrNullString()))), hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(isEmptyOrNullString())), + hasJsonPath("$.token", not(is(emptyOrNullString()))), hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), hasJsonPath("$.allfiles", is(false)), - hasJsonPath("$.requestDate", not(isEmptyOrNullString())), // TODO should be an ISO datetime - hasJsonPath("$._links.self.href", not(isEmptyOrNullString())) + hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), // TODO should be an ISO datetime + hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) ))) .andReturn(); From 6926b49fab5808edfff140b9a8bde35a4add0fa8 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 9 Aug 2021 09:47:11 -0400 Subject: [PATCH 0149/1254] [DS-3952] Match ISO time zone properly. More informative message on failure. --- dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java index f4bd81c993..c544bb74e6 100644 --- a/dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java +++ b/dspace-api/src/test/java/org/dspace/matcher/DateMatcher.java @@ -23,7 +23,7 @@ import org.hamcrest.Description; public class DateMatcher extends BaseMatcher { private static final SimpleDateFormat dateFormat - = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX"); private final Date matchDate; @@ -57,7 +57,8 @@ public class DateMatcher try { testDateDecoded = dateFormat.parse((String)testDate); } catch (ParseException ex) { - throw new IllegalArgumentException("Argument is not an ISO 8601 zoned date", ex); + throw new IllegalArgumentException("Argument '" + testDate + + "' is not an ISO 8601 zoned date", ex); } // Compare with the Date that must match From 816ca59e4cdb2491c1819c16a4d3f0512acc92ed Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 10 Aug 2021 09:24:35 +0200 Subject: [PATCH 0150/1254] 81832: Don't ignore author fields in discovery authority IT --- .../dspace/app/rest/DiscoveryRestControllerIT.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) 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 021006cc02..7e5e6fb7e4 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 @@ -196,15 +196,8 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @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-prefered.author", true); - configurationService.setProperty("discovery.index.authority.ignore-prefered.dc.contributor.author", true); - configurationService.setProperty("discovery.browse.authority.ignore-variants.author", true); - configurationService.setProperty("discovery.index.authority.ignore-variants.dc.contributor.author", true); - + configurationService.setProperty("choices.plugin.dc.contributor.author", "SolrAuthorAuthority"); + configurationService.setProperty("authority.controlled.dc.contributor.author", "true"); metadataAuthorityService.clearCache(); From 6280b76355ae359d811fe87a8d166f5eb97c0768 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 10 Aug 2021 09:54:20 +0200 Subject: [PATCH 0151/1254] 81832: Surround getLabel/getVariant calls with try/catch --- ...lrServiceMetadataBrowseIndexingPlugin.java | 19 ++++++++++++++----- .../indexobject/ItemIndexFactoryImpl.java | 15 +++++++++++---- 2 files changed, 25 insertions(+), 9 deletions(-) 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 081e06d1d9..d03ea359f5 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java @@ -174,8 +174,13 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex Boolean.FALSE), true); if (!ignorePrefered) { - preferedLabel = choiceAuthorityService - .getLabel(values.get(x), collection, values.get(x).getLanguage()); + try { + preferedLabel = choiceAuthorityService + .getLabel(values.get(x), collection, values.get(x).getLanguage()); + } catch (Exception e) { + log.warn("Failed to get preferred label for " + + values.get(x).getMetadataField().toString('.'), e); + } } List variants = null; @@ -193,9 +198,13 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex Boolean.FALSE), true); if (!ignoreVariants) { - variants = choiceAuthorityService - .getVariants( - values.get(x), collection); + try { + variants = choiceAuthorityService + .getVariants(values.get(x), collection); + } catch (Exception e) { + log.warn("Failed to get variants for " + + values.get(x).getMetadataField().toString(), e); + } } if (StringUtils diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 07948bb0c3..5921c47307 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 @@ -359,10 +359,13 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl Date: Tue, 10 Aug 2021 17:14:36 -0400 Subject: [PATCH 0152/1254] [DS-3952] Improve confusing debug log message. --- .../org/dspace/app/requestitem/RequestItemServiceImpl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 451af9bc1b..3025f6e8a8 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 @@ -53,10 +53,8 @@ public class RequestItemServiceImpl implements RequestItemService { requestItemDAO.save(context, requestItem); - if (log.isDebugEnabled()) { - log.debug("Created requestitem_token " + requestItem.getID() - + " with token " + requestItem.getToken() + "\""); - } + log.debug("Created RequestItem with ID {} and token {}", + ()->requestItem.getID(), ()->requestItem.getToken()); return requestItem.getToken(); } From 3f06c7e79c8f4d34bea9dc56b8dbb2fda9202722 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 13 Aug 2021 15:56:26 -0400 Subject: [PATCH 0153/1254] Document that EPerson's password, salt and algorithm setters are related. #3363 --- .../main/java/org/dspace/eperson/EPerson.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPerson.java b/dspace-api/src/main/java/org/dspace/eperson/EPerson.java index 3c48a5244a..def7697632 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPerson.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPerson.java @@ -37,7 +37,6 @@ import org.hibernate.proxy.HibernateProxyHelper; * Class representing an e-person. * * @author David Stuve - * @version $Revision$ */ @Entity @Cacheable @@ -381,6 +380,13 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport { return digestAlgorithm; } + /** + * Store the digest algorithm used to hash the password. You should also + * set the {@link setPassword password hash} and the + * {@link setDigestAlgorithm digest algorithm}. + * + * @param digestAlgorithm + */ void setDigestAlgorithm(String digestAlgorithm) { this.digestAlgorithm = digestAlgorithm; } @@ -389,6 +395,13 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport { return salt; } + /** + * Store the salt used when hashing the password. You should also set the + * {@link setPassword password hash} and the {@link setDigestAlgorithm + * digest algorithm}. + * + * @param salt + */ void setSalt(String salt) { this.salt = salt; } @@ -397,6 +410,12 @@ public class EPerson extends DSpaceObject implements DSpaceObjectLegacySupport { return password; } + /** + * Store the hash of a password. You should also set the + * {@link setSalt salt} and the {@link setDigestAlgorithm digest algorithm}. + * + * @param password + */ void setPassword(String password) { this.password = password; } From dd40f5ff3c0a27c338c53e37ac14ef8af7f1a2c6 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 13 Aug 2021 15:57:52 -0400 Subject: [PATCH 0154/1254] Add option to change user's password. #3363 --- .../org/dspace/eperson/EPersonCLITool.java | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java index aee2e7a082..de3609c5d9 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java @@ -13,6 +13,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -57,8 +58,11 @@ public class EPersonCLITool { private static final Option OPT_NEW_EMAIL = new Option("i", "newEmail", true, "new email address"); private static final Option OPT_NEW_NETID = new Option("I", "newNetid", true, "new network ID"); + private static final Option OPT_NEW_PASSWORD + = new Option("w", "newPassword", false, "prompt for new password"); - private static final EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + private static final EPersonService ePersonService + = EPersonServiceFactory.getInstance().getEPersonService(); /** * Default constructor @@ -120,6 +124,8 @@ public class EPersonCLITool { System.err.println(ex.getMessage()); } } + + System.exit(status); } /** @@ -177,11 +183,11 @@ public class EPersonCLITool { EPerson eperson = null; try { eperson = ePersonService.create(context); - } catch (SQLException ex) { + } catch (SQLException | AuthorizeException ex) { context.abort(); System.err.println(ex.getMessage()); return 1; - } catch (AuthorizeException ex) { /* XXX SNH */ } + } eperson.setCanLogIn(true); eperson.setSelfRegistered(false); @@ -204,11 +210,11 @@ public class EPersonCLITool { try { ePersonService.update(context, eperson); System.out.printf("Created EPerson %s\n", eperson.getID().toString()); - } catch (SQLException ex) { + } catch (SQLException | AuthorizeException ex) { context.abort(); System.err.println(ex.getMessage()); return 1; - } catch (AuthorizeException ex) { /* XXX SNH */ } + } return 0; } @@ -315,6 +321,7 @@ public class EPersonCLITool { options.addOption(OPT_CAN_LOGIN); options.addOption(OPT_NEW_EMAIL); options.addOption(OPT_NEW_NETID); + options.addOption(OPT_NEW_PASSWORD); options.addOption("h", "help", false, "explain --modify options"); @@ -334,11 +341,14 @@ public class EPersonCLITool { // Modify! EPerson eperson = null; + String userName = null; try { if (command.hasOption(OPT_NETID.getOpt())) { - eperson = ePersonService.findByNetid(context, command.getOptionValue(OPT_NETID.getOpt())); + userName = command.getOptionValue(OPT_NETID.getOpt()); + eperson = ePersonService.findByNetid(context, userName); } else if (command.hasOption(OPT_EMAIL.getOpt())) { - eperson = ePersonService.findByEmail(context, command.getOptionValue(OPT_EMAIL.getOpt())); + userName = command.getOptionValue(OPT_EMAIL.getOpt()); + eperson = ePersonService.findByEmail(context, userName); } else { System.err.println("No EPerson selected"); return 1; @@ -361,6 +371,24 @@ public class EPersonCLITool { eperson.setNetid(command.getOptionValue(OPT_NEW_NETID.getOpt())); modified = true; } + if (command.hasOption(OPT_NEW_PASSWORD.getOpt())) { + // TODO prompt, collect password, verify + char[] password = System.console() + .readPassword("Enter new password for user %s", userName); + char[] password2 = System.console() + .readPassword("Enter new password again to verify"); + if (Arrays.equals(password, password2)) { + PasswordHash newHashedPassword = new PasswordHash(String.valueOf(password)); + Arrays.fill(password, '\0'); // Obliterate cleartext passwords + Arrays.fill(password2, '\0'); + eperson.setPassword(newHashedPassword.getHashString()); + eperson.setSalt(newHashedPassword.getSaltString()); + eperson.setDigestAlgorithm(newHashedPassword.getAlgorithm()); + modified = true; + } else { + System.err.println("Passwords do not match. Password not set"); + } + } if (command.hasOption(OPT_GIVENNAME.getOpt())) { eperson.setFirstName(context, command.getOptionValue(OPT_GIVENNAME.getOpt())); modified = true; @@ -387,15 +415,16 @@ public class EPersonCLITool { eperson.setCanLogIn(Boolean.valueOf(command.getOptionValue(OPT_CAN_LOGIN.getOpt()))); modified = true; } + if (modified) { try { ePersonService.update(context, eperson); System.out.printf("Modified EPerson %s\n", eperson.getID().toString()); - } catch (SQLException ex) { + } catch (SQLException | AuthorizeException ex) { context.abort(); System.err.println(ex.getMessage()); return 1; - } catch (AuthorizeException ex) { /* XXX SNH */ } + } } else { System.out.println("No changes."); } From 653be5db0084aa17c9e8ee26c06327c87c680fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 17 Aug 2021 11:44:04 +0100 Subject: [PATCH 0155/1254] new openaire funding external source feature --- dspace-api/pom.xml | 7 + .../external/OpenAIRERestConnector.java | 341 +++++++++++++ .../dspace/external/OpenAIRERestToken.java | 66 +++ .../impl/OpenAIREFundingDataProvider.java | 478 ++++++++++++++++++ dspace/config/dspace.cfg | 1 + dspace/config/modules/openaire-client.cfg | 28 + .../config/spring/api/external-openaire.xml | 19 + 7 files changed, 940 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java create mode 100644 dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java create mode 100644 dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java create mode 100644 dspace/config/modules/openaire-client.cfg create mode 100644 dspace/config/spring/api/external-openaire.xml diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index fdad2d29dc..8882a79af2 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -920,6 +920,13 @@ 6.4.0 + + + eu.openaire + funders-model + 2.0.0 + + diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java new file mode 100644 index 0000000000..954b6ac1e7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -0,0 +1,341 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.external; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import javax.xml.bind.JAXBException; + +import eu.openaire.jaxb.helper.OpenAIREHandler; +import eu.openaire.jaxb.model.Response; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.NoHttpResponseException; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.apache.logging.log4j.Logger; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * based on OrcidRestConnector it's a rest connector for OpenAIRE API providing + * ways to perform searches and token grabbing + * + * @author paulo-graca + * + */ +public class OpenAIRERestConnector { + /** + * log4j logger + */ + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIRERestConnector.class); + + /** + * OpenAIRE API Url + */ + private String url; + + /** + * Boolean with token usage definition true if we want to use a token + */ + boolean tokenUsage; + + /** + * OpenAIRE Authorization and Authentication Token Service URL + */ + private String tokenServiceUrl; + + /** + * OpenAIRE clientId + */ + private String clientId; + + /** + * OpenAIRERest access token + */ + private OpenAIRERestToken accessToken; + + /** + * OpenAIRE clientSecret + */ + private String clientSecret; + + public OpenAIRERestConnector(String url) { + this.url = url; + } + + /** + * This method grabs an accessToken an sets the expiration time Based.
    + * Based on https://develop.openaire.eu/basic.html + * + * @throws IOException + */ + public OpenAIRERestToken grabNewAccessToken() throws IOException { + + if (StringUtils.isBlank(tokenServiceUrl) || StringUtils.isBlank(clientId) + || StringUtils.isBlank(clientSecret)) { + throw new IOException("Cannot grab OpenAIRE token with nulls service url, client id or secret"); + } + + String auth = clientId + ":" + clientSecret; + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes()); + String authHeader = "Basic " + new String(encodedAuth); + + HttpPost httpPost = new HttpPost(tokenServiceUrl); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("User-Agent", "DSpace/7.X"); + httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); + httpPost.setHeader(HttpHeaders.AUTHORIZATION, authHeader); + + // Request parameters and other properties. + List params = new ArrayList(1); + params.add(new BasicNameValuePair("grant_type", "client_credentials")); + httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); + + HttpClient httpClient = HttpClientBuilder.create().build(); + HttpResponse getResponse = httpClient.execute(httpPost); + + JSONObject responseObject = null; + try (InputStream is = getResponse.getEntity().getContent(); + BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { + String inputStr; + // verify if we have basic json + while ((inputStr = streamReader.readLine()) != null && responseObject == null) { + if (inputStr.startsWith("{") && inputStr.endsWith("}") && inputStr.contains("access_token") + && inputStr.contains("expires_in")) { + try { + responseObject = new JSONObject(inputStr); + } catch (Exception e) { + // Not as valid as I'd hoped, move along + responseObject = null; + } + } + } + } + if (responseObject == null || !responseObject.has("access_token") || !responseObject.has("expires_in")) { + throw new IOException("Unable to grab the access token using provided service url, client id and secret"); + } + + return new OpenAIRERestToken(responseObject.get("access_token").toString(), + Long.valueOf(responseObject.get("expires_in").toString())); + + } + + /** + * Perform a GET request to the OpenAIRE API + * + * @param file + * @param accessToken + * @return an InputStream with a Result + */ + public InputStream get(String file, String accessToken) { + HttpResponse getResponse = null; + InputStream result = null; + file = trimSlashes(file); + + try { + URL fullPath = new URL(url + '/' + file); + + log.debug("Requesting: " + fullPath.toString()); + + HttpGet httpGet = new HttpGet(fullPath.toURI()); + if (StringUtils.isNotBlank(accessToken)) { + httpGet.addHeader("Authorization", "Bearer " + accessToken); + } + + HttpClient httpClient = HttpClientBuilder.create().build(); + getResponse = httpClient.execute(httpGet); + + StatusLine status = getResponse.getStatusLine(); + + // registering errors + switch (status.getStatusCode()) { + case HttpStatus.SC_NOT_FOUND: + // 404 - Not found + case HttpStatus.SC_FORBIDDEN: + // 403 - Invalid Access Token + case 429: + // 429 - Rate limit abuse for unauthenticated user + Header[] limitUsed = getResponse.getHeaders("x-ratelimit-used"); + Header[] limitMax = getResponse.getHeaders("x-ratelimit-limit"); + + if (limitUsed.length > 0) { + String limitMsg = limitUsed[0].getValue(); + if (limitMax.length > 0) { + limitMsg = limitMsg.concat(" of " + limitMax[0].getValue()); + } + getGotError( + new NoHttpResponseException(status.getReasonPhrase() + " with usage limit " + limitMsg), + url + '/' + file); + } else { + // 429 - Rate limit abuse + getGotError(new NoHttpResponseException(status.getReasonPhrase()), url + '/' + file); + } + break; + default: + // 200 or other + break; + } + + // do not close this httpClient + result = getResponse.getEntity().getContent(); + } catch (MalformedURLException e1) { + // TODO Auto-generated catch block + getGotError(e1, url + '/' + file); + } catch (Exception e) { + getGotError(e, url + '/' + file); + } + + return result; + } + + /** + * Perform an OpenAIRE Project Search By Keywords + * + * @param page + * @param size + * @param keywords + * @return OpenAIRE Response + */ + public Response searchProjectByKeywords(int page, int size, String... keywords) { + String path = "search/projects?keywords=" + String.join("+", keywords); + return search(path, page, size); + } + + /** + * Perform an OpenAIRE Project Search By ID and by Funder + * + * @param projectID + * @param projectFunder + * @param page + * @param size + * @return OpenAIRE Response + */ + public Response searchProjectByIDAndFunder(String projectID, String projectFunder, int page, int size) { + String path = "search/projects?grantID=" + projectID + "&funder=" + projectFunder; + return search(path, page, size); + } + + /** + * Perform an OpenAIRE Search request + * + * @param path + * @param page + * @param size + * @return OpenAIRE Response + */ + public Response search(String path, int page, int size) { + String[] queryStringPagination = { "page=" + page, "size=" + size }; + + String queryString = path + ((path.indexOf("?") > 0) ? "&" : "?") + String.join("&", queryStringPagination); + + InputStream result = null; + if (tokenUsage) { + try { + if (accessToken == null) { + accessToken = this.grabNewAccessToken(); + } else if (!accessToken.isValidToken()) { + accessToken = this.grabNewAccessToken(); + } + + result = get(queryString, accessToken.getToken()); + } catch (IOException e) { + log.error("Error grabbing the token: " + e.getMessage()); + getGotError(e, path); + } + } else { + result = get(queryString, null); + } + + if (result != null) { + try { + return OpenAIREHandler.unmarshal(result); + } catch (JAXBException e) { + log.error("Error extracting result from request: " + queryString); + getGotError(e, path); + } + } + return null; + } + + /** + * trim slashes from the path + * + * @param path + * @return string path without trailing slashes + */ + public static String trimSlashes(String path) { + while (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + while (path.startsWith("/")) { + path = path.substring(1); + } + return path; + } + + /** + * stores clientId to grab the token + * + * @param clientId + */ + @Autowired(required = false) + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * stores tokenServiceUrl to grab the token + * + * @param tokenServiceUrl + */ + @Autowired(required = false) + public void setTokenServiceUrl(String tokenServiceUrl) { + this.tokenServiceUrl = tokenServiceUrl; + } + + /** + * stores clientSecret to grab the token + * + * @param clientSecret + */ + @Autowired(required = false) + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + /** + * tokenUsage true to enable the usage of an access token + * + * @param tokenUsage + */ + @Autowired(required = false) + public void setTokenUsage(boolean tokenUsage) { + this.tokenUsage = tokenUsage; + } + + protected void getGotError(Exception e, String fullPath) { + log.error("Error in rest connector for path: " + fullPath, e); + } +} diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.java new file mode 100644 index 0000000000..203f09b3c6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestToken.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.external; + +/** + * OpenAIRE rest API token to be used when grabbing an accessToken.
    + * Based on https://develop.openaire.eu/basic.html + * + * @author paulo-graca + * + */ +public class OpenAIRERestToken { + + /** + * Stored access token + */ + private String accessToken; + + /** + * Stored expiration period (in seconds) + */ + private Long accessTokenExpiration = 0L; + + /** + * Stores the grabbed token + * + * @param accessToken + * @param expiresIn + */ + public OpenAIRERestToken(String accessToken, Long expiresIn) { + this.accessToken = accessToken; + this.setExpirationDate(expiresIn); + } + + /** + * Returns the stored + * + * @return String with the stored token + */ + public String getToken() { + return this.accessToken; + } + + /** + * If the existing token has an expiration date and if it is at a minute of + * expiring + * + * @return + */ + public boolean isValidToken() { + if (this.accessToken == null) { + return false; + } + + return ((accessTokenExpiration - (60 * 1000)) > System.currentTimeMillis()); + } + + private void setExpirationDate(Long expiresIn) { + accessTokenExpiration = System.currentTimeMillis() + (expiresIn * 1000L); + } +} diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java new file mode 100644 index 0000000000..51bb4706a5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java @@ -0,0 +1,478 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.external.provider.impl; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import eu.openaire.jaxb.helper.FundingHelper; +import eu.openaire.jaxb.helper.ProjectHelper; +import eu.openaire.jaxb.model.Response; +import eu.openaire.jaxb.model.Result; +import eu.openaire.oaf.model.base.FunderType; +import eu.openaire.oaf.model.base.FundingTreeType; +import eu.openaire.oaf.model.base.FundingType; +import eu.openaire.oaf.model.base.Project; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.external.OpenAIRERestConnector; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.ExternalDataProvider; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class is the implementation of the ExternalDataProvider interface that + * will deal with the OpenAIRE External Data lookup + * + * @author paulo-graca + * + */ +public class OpenAIREFundingDataProvider implements ExternalDataProvider { + + /** + * log4j logger + */ + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIREFundingDataProvider.class); + + /** + * GrantAgreement prefix + */ + protected static final String PREFIX = "info:eu-repo/grantAgreement"; + + /** + * rows default limit + */ + protected static final int LIMIT_DEFAULT = 10; + + /** + * Source identifier (defined in beans) + */ + protected String sourceIdentifier; + + /** + * Connector to handle token and requests + */ + protected OpenAIRERestConnector connector; + + /** + * required method + */ + public void init() throws IOException { + } + + @Override + public String getSourceIdentifier() { + return sourceIdentifier; + } + + @Override + public Optional getExternalDataObject(String id) { + Response response = searchByProjectURI(id); + + try { + if (response.getHeader() != null && Integer.parseInt(response.getHeader().getTotal()) > 0) { + Project project = response.getResults().getResult().get(0).getMetadata().getEntity().getProject(); + ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider.ExternalDataObjectBuilder( + project).setId(generateProjectURI(project)).setSource(sourceIdentifier).build(); + return Optional.of(externalDataObject); + } + } catch (NumberFormatException e) { + log.error("Invalid Total from response - " + e.getMessage()); + } + + return Optional.empty(); + } + + @Override + public List searchExternalDataObjects(String query, int start, int limit) { + + // ensure we have a positive > 0 limit + if (limit < 1) { + limit = LIMIT_DEFAULT; + } + + // OpenAIRE uses pages and first page starts with 1 + int page = (start / limit) + 1; + + // escaping query + String encodedQuery = encodeValue(query); + + Response projectResponse = connector.searchProjectByKeywords(page, limit, encodedQuery); + + if (projectResponse == null || projectResponse.getResults() == null) { + return Collections.emptyList(); + } + + List projects = new ArrayList(); + for (Result result : projectResponse.getResults().getResult()) { + + if (result.getMetadata() != null && result.getMetadata().getEntity() != null + && result.getMetadata().getEntity().getProject() != null) { + projects.add(result.getMetadata().getEntity().getProject()); + } else { + throw new IllegalStateException("No project found"); + } + } + + if (projects.size() > 0) { + return projects.stream() + .map(project -> new OpenAIREFundingDataProvider.ExternalDataObjectBuilder(project) + .setId(generateProjectURI(project)).setSource(sourceIdentifier).build()) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + @Override + public boolean supports(String source) { + return StringUtils.equalsIgnoreCase(sourceIdentifier, source); + } + + @Override + public int getNumberOfResults(String query) { + Response projectResponse = connector.searchProjectByKeywords(0, 0, query); + return Integer.parseInt(projectResponse.getHeader().getTotal()); + } + + /** + * Generic setter for the sourceIdentifier + * + * @param sourceIdentifier The sourceIdentifier to be set on this + * OpenAIREFunderDataProvider + */ + @Autowired(required = true) + public void setSourceIdentifier(String sourceIdentifier) { + this.sourceIdentifier = sourceIdentifier; + } + + public OpenAIRERestConnector getConnector() { + return connector; + } + + /** + * Generic setter for OpenAIRERestConnector + * + * @param connector + */ + @Autowired(required = true) + public void setConnector(OpenAIRERestConnector connector) { + this.connector = connector; + } + + /** + * + * @param projectURI from type + * info:eu-repo/grantAgreement/FCT/3599-PPCDT/82130/PT + * @return Response + */ + public Response searchByProjectURI(String projectURI) { + String[] splittedURI = projectURI.replaceAll(PREFIX, "").split("/"); + return connector.searchProjectByIDAndFunder(splittedURI[3], splittedURI[1], 1, 1); + } + + /** + * This method returns an URI based on OpenAIRE 3.0 guidelines + * https://guidelines.openaire.eu/en/latest/literature/field_projectid.html that + * can be used as an ID if is there any missing part, that part it will be + * replaced by the character '+' + * + * @param project + * @return String with an URI like: info:eu-repo/grantAgreement/EC/FP7/244909 + */ + private static String generateProjectURI(Project project) { + ProjectHelper projectHelper = new ProjectHelper(project.getCodeOrTitleOrAcronym()); + + String prefix = PREFIX; + String funderShortName = "+"; + String fundingName = "+"; + String code = "+"; + String jurisdiction = "+"; + + Optional fundingTree = projectHelper.getFundingTreeTypes().stream().findFirst(); + if (!fundingTree.isEmpty()) { + if (fundingTree.get().getFunder() != null) { + if (fundingTree.get().getFunder().getShortname() != null) { + funderShortName = encodeValue(fundingTree.get().getFunder().getShortname()); + } + if (fundingTree.get().getFunder().getJurisdiction() != null) { + jurisdiction = encodeValue(fundingTree.get().getFunder().getJurisdiction()); + } + } + FundingHelper fundingHelper = new FundingHelper( + fundingTree.get().getFundingLevel2OrFundingLevel1OrFundingLevel0()); + Optional funding = fundingHelper.getFirstAvailableFunding().stream().findFirst(); + + if (!funding.isEmpty()) { + fundingName = encodeValue(funding.get().getName()); + } + + } + + Optional optCode = projectHelper.getCodes().stream().findFirst(); + if (!optCode.isEmpty()) { + code = encodeValue(optCode.get()); + } + + return String.format("%s/%s/%s/%s/%s", prefix, funderShortName, fundingName, code, jurisdiction); + } + + private static String encodeValue(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + return value; + } + } + + /** + * OpenAIRE Funding External Data Builder Class + * + * @author pgraca + * + */ + public static class ExternalDataObjectBuilder { + ExternalDataObject externalDataObject; + + public ExternalDataObjectBuilder(Project project) { + String funderIdPrefix = "urn:openaire:"; + this.externalDataObject = new ExternalDataObject(); + + ProjectHelper projectHelper = new ProjectHelper(project.getCodeOrTitleOrAcronym()); + for (FundingTreeType fundingTree : projectHelper.getFundingTreeTypes()) { + FunderType funder = fundingTree.getFunder(); + // Funder name + this.addFunderName(funder.getName()); + // Funder Id - convert it to an urn + this.addFunderID(funderIdPrefix + funder.getId()); + // Jurisdiction + this.addFunderJuristiction(funder.getJurisdiction()); + + FundingHelper fundingHelper = new FundingHelper( + fundingTree.getFundingLevel2OrFundingLevel1OrFundingLevel0()); + + // Funding description + for (FundingType funding : fundingHelper.getFirstAvailableFunding()) { + this.addFundingStream(funding.getDescription()); + } + } + + // Title + for (String title : projectHelper.getTitles()) { + this.addAwardTitle(title); + this.setDisplayValue(title); + this.setValue(title); + } + + // Code + for (String code : projectHelper.getCodes()) { + this.addAwardNumber(code); + } + + // Website url + for (String url : projectHelper.getWebsiteUrls()) { + this.addAwardURI(url); + } + + // Acronyms + for (String acronym : projectHelper.getAcronyms()) { + this.addFundingItemAcronym(acronym); + } + + // Keywords + for (String keyword : projectHelper.getKeywords()) { + this.addSubject(keyword); + } + } + + /** + * Set the external data source + * + * @param source + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder setSource(String source) { + this.externalDataObject.setSource(source); + return this; + } + + /** + * Set the external data display name + * + * @param displayName + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder setDisplayValue(String displayName) { + this.externalDataObject.setDisplayValue(displayName); + return this; + } + + /** + * Set the external data value + * + * @param value + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder setValue(String value) { + this.externalDataObject.setValue(value); + return this; + } + + /** + * Set the external data id + * + * @param id + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder setId(String id) { + this.externalDataObject.setId(id); + return this; + } + + /** + * Add metadata dc.identifier + * + * @param metadata identifier + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addIdentifier(String identifier) { + this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "identifier", null, null, identifier)); + return this; + } + + /** + * Add metadata project.funder.name + * + * @param metadata funderName + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addFunderName(String funderName) { + this.externalDataObject.addMetadata(new MetadataValueDTO("project", "funder", "name", null, funderName)); + return this; + } + + /** + * Add metadata project.funder.identifier + * + * @param metadata funderId + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addFunderID(String funderID) { + this.externalDataObject + .addMetadata(new MetadataValueDTO("project", "funder", "identifier", null, funderID)); + return this; + } + + /** + * Add metadata dc.title + * + * @param metadata awardTitle + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addAwardTitle(String awardTitle) { + this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "title", null, null, awardTitle)); + return this; + } + + /** + * Add metadata oaire.fundingStream + * + * @param metadata fundingStream + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addFundingStream(String fundingStream) { + this.externalDataObject + .addMetadata(new MetadataValueDTO("oaire", "fundingStream", null, null, fundingStream)); + return this; + } + + /** + * Add metadata oaire.awardNumber + * + * @param metadata awardNumber + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addAwardNumber(String awardNumber) { + this.externalDataObject.addMetadata(new MetadataValueDTO("oaire", "awardNumber", null, null, awardNumber)); + return this; + } + + /** + * Add metadata oaire.awardURI + * + * @param metadata websiteUrl + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addAwardURI(String websiteUrl) { + this.externalDataObject.addMetadata(new MetadataValueDTO("oaire", "awardURI", null, null, websiteUrl)); + return this; + } + + /** + * Add metadata dc.title.alternative + * + * @param metadata fundingItemAcronym + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addFundingItemAcronym(String fundingItemAcronym) { + this.externalDataObject + .addMetadata(new MetadataValueDTO("dc", "title", "alternative", null, fundingItemAcronym)); + return this; + } + + /** + * Add metadata dc.coverage.spatial + * + * @param metadata funderJuristiction + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addFunderJuristiction(String funderJuristiction) { + this.externalDataObject + .addMetadata(new MetadataValueDTO("dc", "coverage", "spatial", null, funderJuristiction)); + return this; + } + + /** + * Add metadata dc.description + * + * @param metadata description + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addDescription(String description) { + this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "description", null, null, description)); + return this; + } + + /** + * Add metadata dc.subject + * + * @param metadata subject + * @return ExternalDataObjectBuilder + */ + public ExternalDataObjectBuilder addSubject(String subject) { + this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "subject", null, null, subject)); + return this; + } + + /** + * Build the External Data + * + * @return ExternalDataObject + */ + public ExternalDataObject build() { + return this.externalDataObject; + } + } +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 122cf9c044..c5dbae0e64 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1566,6 +1566,7 @@ include = ${module_dir}/google-analytics.cfg include = ${module_dir}/healthcheck.cfg include = ${module_dir}/irus-statistics.cfg include = ${module_dir}/oai.cfg +include = ${module_dir}/openaire-client.cfg include = ${module_dir}/rdf.cfg include = ${module_dir}/rest.cfg include = ${module_dir}/solr-statistics.cfg diff --git a/dspace/config/modules/openaire-client.cfg b/dspace/config/modules/openaire-client.cfg new file mode 100644 index 0000000000..34cbd993f2 --- /dev/null +++ b/dspace/config/modules/openaire-client.cfg @@ -0,0 +1,28 @@ +#---------------------------------------------------------------# +#----------------OPENAIRE CLIENT CONFIGURATIONS-----------------# +#---------------------------------------------------------------# +# These configs are used by the OPENAIRE rest client # +#---------------------------------------------------------------# + +# In order to use the API without the imposed number of requests limitation +# you will need to use an accessToken. +# The accessToken it only has a validity of one hour +# For more details about the token, please check: https://develop.openaire.eu/personalToken.html + +# the current OpenAIRE Rest client implementation uses basic authentication +# Described here: https://develop.openaire.eu/basic.html + +# ---- Token usage required definitions ---- +# you can override this settings in your local.cfg file +openaire.aai.tokenUsage = false + +# URL of the OpenAIRE authentication and authorization service +openaire.aai.url = https://aai.openaire.eu/oidc/token + +# you will be required to register at OpenAIRE (https://services.openaire.eu/uoa-user-management/registeredServices) +# and create your service in order to get the following data: +openaire.aai.clientId = CLIENT_ID_HERE +openaire.aai.clientSecret = CLIENT_SECRET_HERE + +# URL of OpenAIRE Rest API +openaire.api.url = https://api.openaire.eu \ No newline at end of file diff --git a/dspace/config/spring/api/external-openaire.xml b/dspace/config/spring/api/external-openaire.xml new file mode 100644 index 0000000000..719decd777 --- /dev/null +++ b/dspace/config/spring/api/external-openaire.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + From b59790a156bf7bac6d6e6118e35ba0aeb6cf6d81 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 17 Aug 2021 11:25:24 -0400 Subject: [PATCH 0156/1254] Add integration test for this feature only. #3363 --- dspace-api/pom.xml | 6 ++ .../org/dspace/eperson/EPersonCLITool.java | 29 ++++++--- .../java/org/dspace/util/ConsoleService.java | 17 +++++ .../org/dspace/util/ConsoleServiceImpl.java | 22 +++++++ .../org/dspace/eperson/EPersonCLIToolIT.java | 64 +++++++++++++++++++ .../dspace/util/FakeConsoleServiceImpl.java | 40 ++++++++++++ 6 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/util/ConsoleService.java create mode 100644 dspace-api/src/main/java/org/dspace/util/ConsoleServiceImpl.java create mode 100644 dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java create mode 100644 dspace-api/src/test/java/org/dspace/util/FakeConsoleServiceImpl.java diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index fdad2d29dc..81303d3785 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -920,6 +920,12 @@ 6.4.0
    + + com.github.stefanbirkner + system-rules + 1.19.0 + test + diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java index de3609c5d9..5b6b28882e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java @@ -29,6 +29,8 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; +import org.dspace.util.ConsoleService; +import org.dspace.util.ConsoleServiceImpl; public class EPersonCLITool { @@ -64,10 +66,13 @@ public class EPersonCLITool { private static final EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + private static ConsoleService consoleService + = new ConsoleServiceImpl(); + /** * Default constructor */ - private EPersonCLITool() { } + EPersonCLITool() { } /** * Tool for manipulating user accounts. @@ -372,14 +377,11 @@ public class EPersonCLITool { modified = true; } if (command.hasOption(OPT_NEW_PASSWORD.getOpt())) { - // TODO prompt, collect password, verify - char[] password = System.console() - .readPassword("Enter new password for user %s", userName); - char[] password2 = System.console() - .readPassword("Enter new password again to verify"); - if (Arrays.equals(password, password2)) { - PasswordHash newHashedPassword = new PasswordHash(String.valueOf(password)); - Arrays.fill(password, '\0'); // Obliterate cleartext passwords + char[] password1 = consoleService.readPassword("Enter new password for user %s", userName); + char[] password2 = consoleService.readPassword("Enter new password again to verify"); + if (Arrays.equals(password1, password2)) { + PasswordHash newHashedPassword = new PasswordHash(String.valueOf(password1)); + Arrays.fill(password1, '\0'); // Obliterate cleartext passwords Arrays.fill(password2, '\0'); eperson.setPassword(newHashedPassword.getHashString()); eperson.setSalt(newHashedPassword.getSaltString()); @@ -456,4 +458,13 @@ public class EPersonCLITool { return 0; } + + /** + * Replace the ConsoleService for testing. + * + * @param service new ConsoleService to be used henceforth. + */ + void setConsoleService(ConsoleService service) { + consoleService = service; + } } diff --git a/dspace-api/src/main/java/org/dspace/util/ConsoleService.java b/dspace-api/src/main/java/org/dspace/util/ConsoleService.java new file mode 100644 index 0000000000..98dc88d546 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/ConsoleService.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.util; + +/** + * Make System.console mock-able for testing. + * + * @author Mark H. Wood + */ +public interface ConsoleService { + public char[] readPassword(String prompt, Object... args); +} diff --git a/dspace-api/src/main/java/org/dspace/util/ConsoleServiceImpl.java b/dspace-api/src/main/java/org/dspace/util/ConsoleServiceImpl.java new file mode 100644 index 0000000000..a58a87f37b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/ConsoleServiceImpl.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.util; + +/** + * Standard implementation of console IO using {@code System.console()}. + * + * @author Mark H. Wood + */ +public class ConsoleServiceImpl + implements ConsoleService { + @Override + public char[] readPassword(String prompt, Object... args) { + return System.console().readPassword(prompt, args); + } +} diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java new file mode 100644 index 0000000000..9ecbd9feb4 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.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.eperson; + +import static org.junit.Assert.assertNotEquals; + +import org.dspace.AbstractIntegrationTest; +import org.dspace.util.FakeConsoleServiceImpl; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.ExpectedSystemExit; + +/** + * + * @author Mark H. Wood + */ +public class EPersonCLIToolIT + extends AbstractIntegrationTest { + private static final String NEW_PASSWORD = "secret"; + + // Handle System.exit() from unit under test. + @Rule + public final ExpectedSystemExit exit = ExpectedSystemExit.none(); + + /** + * Test --modify --newPassword + * @throws Exception passed through. + */ + @Test + @SuppressWarnings("static-access") + public void testSetPassword() + throws Exception { + exit.expectSystemExitWithStatus(0); + System.out.println("main"); + + // Create a source of "console" input. + FakeConsoleServiceImpl consoleService = new FakeConsoleServiceImpl(); + consoleService.setPassword("secret".toCharArray()); + + // Make certain that we know the eperson's email and old password hash. + String email = eperson.getEmail(); + String oldPasswordHash = eperson.getPassword(); + + // Instantiate the unit under test. + EPersonCLITool instance = new EPersonCLITool(); + instance.setConsoleService(consoleService); + + // Test! + String[] argv = { + "--modify", + "--email", email, + "--newPassword", NEW_PASSWORD + }; + instance.main(argv); + + String newPasswordHash = eperson.getPassword(); + assertNotEquals("Password hash did not change", oldPasswordHash, newPasswordHash); + } +} diff --git a/dspace-api/src/test/java/org/dspace/util/FakeConsoleServiceImpl.java b/dspace-api/src/test/java/org/dspace/util/FakeConsoleServiceImpl.java new file mode 100644 index 0000000000..e9220ccef7 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/util/FakeConsoleServiceImpl.java @@ -0,0 +1,40 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.util; + +/** + * A test version of ConsoleService which supplies any password input that we + * want. + * + * @author Mark H. Wood + */ +public class FakeConsoleServiceImpl + implements ConsoleService { + private String prompt; + private Object[] args; + private char[] password; + + @Override + public char[] readPassword(String prompt, Object... args) { + this.prompt = prompt; + this.args = args; + return this.password; + } + + public String getPasswordPrompt() { + return prompt; + } + + public Object[] getArgs() { + return this.args; + } + + public void setPassword(char[] password) { + this.password = password; + } +} From ab8499d17b28a394dc4ec2f3e13e46c44d345c30 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 17 Aug 2021 12:29:11 -0400 Subject: [PATCH 0157/1254] [DS-3952] Don't require authorization. Test this properly. --- .../repository/RequestItemRepository.java | 4 +- .../app/rest/RequestItemRepositoryIT.java | 113 +++++++++++++++++- 2 files changed, 112 insertions(+), 5 deletions(-) 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 22ddf02343..0eef3f0077 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 @@ -30,6 +30,7 @@ import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -53,7 +54,7 @@ public class RequestItemRepository protected RequestItemConverter requestItemConverter; @Override - //@PreAuthorize(expr) + @PreAuthorize("permitAll()") public RequestItemRest findOne(Context context, String id) { RequestItem requestItem = requestItemService.findByToken(context, id); if (null == requestItem) { @@ -70,6 +71,7 @@ public class RequestItemRepository } @Override + @PreAuthorize("permitAll()") public RequestItemRest createAndReturn(Context ctx) { // Fill a RequestItemRest from the client's HTTP request. HttpServletRequest req = getRequestService() 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 e779e41641..4f71df1a54 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 @@ -73,11 +73,12 @@ public class RequestItemRepositoryIT } /** - * Test of findOne method, of class RequestItemRepository. + * Test of findOne method, with an authenticated user. + * * @throws java.lang.Exception passed through. */ @Test - public void testFindOne() + public void testFindOneAuthenticated() throws Exception { System.out.println("findOne"); @@ -112,14 +113,53 @@ public class RequestItemRepositoryIT } /** - * Test of createAndReturn method, of class RequestItemRepository. + * Test of findOne method, with an UNauthenticated user. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testFindOneNotAuthenticated() + throws Exception { + System.out.println("findOne"); + + context.turnOffAuthorisationSystem(); + + // Create necessary supporting objects. + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .build(); + Item item = ItemBuilder.createItem(context, collection) + .build(); + InputStream is = new ByteArrayInputStream(new byte[0]); + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) + .build(); + + // Create a request. + RequestItem request = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .build(); + + // Test: can we find it? + final String uri = URI_ROOT + '/' + request.getToken(); + getClient().perform(get(uri)) + .andExpect(status().isOk()) // Can we find it? + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + RequestCopyMatcher.matchRequestCopy(request)))); + + // Clean up. + bitstream.setDeleted(true); + context.restoreAuthSystemState(); + } + + /** + * Test of createAndReturn method, with an authenticated user. * * @throws java.sql.SQLException passed through. * @throws org.dspace.authorize.AuthorizeException passed through. * @throws java.io.IOException passed through. */ @Test - public void testCreateAndReturn() + public void testCreateAndReturnAuthenticated() throws SQLException, AuthorizeException, IOException, Exception { System.out.println("createAndReturn"); @@ -176,6 +216,71 @@ public class RequestItemRepositoryIT context.restoreAuthSystemState(); } + /** + * Test of createAndReturn method, with an UNauthenticated user. + * This should succeed: anyone can file a request. + * + * @throws java.sql.SQLException passed through. + * @throws org.dspace.authorize.AuthorizeException passed through. + * @throws java.io.IOException passed through. + */ + @Test + public void testCreateAndReturnNotAuthenticated() + throws SQLException, AuthorizeException, IOException, Exception { + System.out.println("createAndReturn"); + + context.turnOffAuthorisationSystem(); + + // Create some necessary objects. + Collection col = CollectionBuilder.createCollection(context, + parentCommunity).build(); + Item item = ItemBuilder.createItem(context, col).build(); + InputStream is = new ByteArrayInputStream(new byte[0]); + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("/dev/null") + .withMimeType("text/plain") + .build(); + + // Fake up a request in REST form. + RequestItemRest rir = new RequestItemRest(); + rir.setBitstreamId(bitstream.getID().toString()); + rir.setItemId(item.getID().toString()); + rir.setRequestEmail(RequestItemBuilder.REQ_EMAIL); + rir.setRequestMessage(RequestItemBuilder.REQ_MESSAGE); + rir.setRequestName(RequestItemBuilder.REQ_NAME); + rir.setAllfiles(false); + + // Create it and see if it was created correctly. + ObjectMapper mapper = new ObjectMapper(); + MvcResult mvcResult = getClient() + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(is(emptyOrNullString()))), + hasJsonPath("$.type", is(RequestItemRest.NAME)), + hasJsonPath("$.token", not(is(emptyOrNullString()))), + hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), + hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), + hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), + hasJsonPath("$.allfiles", is(false)), + hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), // TODO should be an ISO datetime + hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) + ))) + .andReturn(); + + // Clean up the created request. + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String requestToken = String.valueOf(map.get("token")); + RequestItem ri = requestItemService.findByToken(context, requestToken); + requestItemService.delete(context, ri); + + context.restoreAuthSystemState(); + } + /** * Test of getDomainClass method, of class RequestItemRepository. */ From 6f82111d75e3d4a4f7d193c93e87cd06031d8aed Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 17 Aug 2021 12:44:37 -0400 Subject: [PATCH 0158/1254] [DS-3952] Address Checkstyle and NetBeans warnings. --- .../java/org/dspace/app/requestitem/RequestItemServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3025f6e8a8..ebcb7e3dee 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 @@ -54,7 +54,7 @@ public class RequestItemServiceImpl implements RequestItemService { requestItemDAO.save(context, requestItem); log.debug("Created RequestItem with ID {} and token {}", - ()->requestItem.getID(), ()->requestItem.getToken()); + requestItem::getID, requestItem::getToken); return requestItem.getToken(); } From bf80dc02f5c77a8a0dec714c7d77d852eac03e3b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 18 Aug 2021 19:10:36 +0200 Subject: [PATCH 0159/1254] [CST-4469] extended versioning end point --- .../versioning/VersioningServiceImpl.java | 30 +++- .../org/dspace/versioning/dao/VersionDAO.java | 4 +- .../versioning/dao/impl/VersionDAOImpl.java | 13 +- .../versioning/service/VersioningService.java | 23 ++- .../org/dspace/builder/AbstractBuilder.java | 5 + .../org/dspace/app/rest/model/ItemRest.java | 5 + .../ItemVersionHistoryLinkRepository.java | 64 ++++++++ .../repository/VersionRestRepository.java | 137 +++++++++++++++++- .../repository/VersionsLinkRepository.java | 3 +- .../ExternalSourceItemUriListHandler.java | 77 ++++++++++ .../dspace/app/rest/ItemRestRepositoryIT.java | 136 +++++++++++++++++ .../eperson/DeleteEPersonSubmitterIT.java | 2 +- 12 files changed, 487 insertions(+), 12 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java index ee6adf9098..7764e78844 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java @@ -96,7 +96,7 @@ public class VersioningServiceImpl implements VersioningService { } @Override - public void removeVersion(Context c, Version version) throws SQLException { + public void delete(Context c, Version version) throws SQLException { try { // we will first delete the version and then the item // after deletion of the version we cannot find the item anymore @@ -158,7 +158,7 @@ public class VersioningServiceImpl implements VersioningService { public void removeVersion(Context c, Item item) throws SQLException { Version version = versionDAO.findByItem(c, item); if (version != null) { - removeVersion(c, version); + delete(c, version); } } @@ -196,8 +196,11 @@ public class VersioningServiceImpl implements VersioningService { int versionNumber) { try { Version version = versionDAO.create(context, new Version()); - - version.setVersionNumber(getNextVersionNumer(context, history)); + if (versionNumber > 0) { + version.setVersionNumber(versionNumber); + } else { + version.setVersionNumber(getNextVersionNumer(context, history)); + } version.setVersionDate(date); version.setePerson(item.getSubmitter()); version.setItem(item); @@ -213,10 +216,14 @@ public class VersioningServiceImpl implements VersioningService { @Override public List getVersionsByHistory(Context c, VersionHistory vh) throws SQLException { - List versions = versionDAO.findVersionsWithItems(c, vh); + List versions = versionDAO.findVersionsWithItems(c, vh, -1, -1); return versions; } + @Override + public List getVersionsByHistory(Context c, VersionHistory vh, int offset, int limit) throws SQLException { + return versionDAO.findVersionsWithItems(c, vh, offset, limit); + } // **** PROTECTED METHODS!! @@ -236,4 +243,17 @@ public class VersioningServiceImpl implements VersioningService { return next; } + + @Override + public void update(Context context, Version version) throws SQLException { + if (version != null) { + versionDAO.save(context, version); + } + } + + @Override + public int countVersionsByHistory(Context context, VersionHistory versionHistory) throws SQLException { + return versionDAO.countVersionsByHistory(context, versionHistory); + } + } diff --git a/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java b/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java index 52bcb978f1..f041070e1e 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java +++ b/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java @@ -39,9 +39,11 @@ public interface VersionDAO extends GenericDAO { * @return all versions of an version history that have items assigned. * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List findVersionsWithItems(Context context, VersionHistory versionHistory) + public List findVersionsWithItems(Context context, VersionHistory versionHistory, int offset, int limit) throws SQLException; public int getNextVersionNumber(Context c, VersionHistory vh) throws SQLException; + public int countVersionsByHistory(Context context, VersionHistory versionHistory) throws SQLException; + } diff --git a/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java b/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java index 6633c892ea..e6eac4cb99 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java @@ -61,7 +61,7 @@ public class VersionDAOImpl extends AbstractHibernateDAO implements Ver } @Override - public List findVersionsWithItems(Context context, VersionHistory versionHistory) + public List findVersionsWithItems(Context context, VersionHistory versionHistory, int offset, int limit) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); @@ -78,6 +78,15 @@ public class VersionDAOImpl extends AbstractHibernateDAO implements Ver orderList.add(criteriaBuilder.desc(versionRoot.get(Version_.versionNumber))); criteriaQuery.orderBy(orderList); - return list(context, criteriaQuery, false, Version.class, -1, -1); + return list(context, criteriaQuery, false, Version.class, limit, offset); } + + @Override + public int countVersionsByHistory(Context context, VersionHistory versionHistory) throws SQLException { + Query query = createQuery(context, "SELECT count(*) FROM " + Version.class.getSimpleName() + + " WHERE versionhistory_id = (:versionhistoryId)"); + query.setParameter("versionhistoryId", versionHistory); + return count(query); + } + } diff --git a/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java b/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java index 56d52e3953..8a85f960bd 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java +++ b/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java @@ -40,7 +40,16 @@ public interface VersioningService { */ List getVersionsByHistory(Context c, VersionHistory vh) throws SQLException; - void removeVersion(Context c, Version version) throws SQLException; + List getVersionsByHistory(Context c, VersionHistory vh, int offset, int limit) throws SQLException; + + /** + * Delete a Version + * + * @param context context + * @param Version version + * @throws SQLException if database error + */ + public void delete(Context c, Version version) throws SQLException; void removeVersion(Context c, Item item) throws SQLException; @@ -56,4 +65,16 @@ public interface VersioningService { Version createNewVersion(Context context, VersionHistory history, Item item, String summary, Date date, int versionNumber); + + /** + * Update the Version + * + * @param context context + * @param Version version + * @throws SQLException if database error + */ + public void update(Context context, Version version) throws SQLException; + + public int countVersionsByHistory(Context context, VersionHistory versionHistory) throws SQLException; + } 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 1f8a9e4a5b..e4370167cd 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -44,6 +44,7 @@ import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; +import org.dspace.versioning.service.VersioningService; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.service.XmlWorkflowService; import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; @@ -89,6 +90,7 @@ public abstract class AbstractBuilder { static RelationshipTypeService relationshipTypeService; static EntityTypeService entityTypeService; static ProcessService processService; + static VersioningService versioningService; protected Context context; @@ -136,6 +138,8 @@ public abstract class AbstractBuilder { relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); processService = ScriptServiceFactory.getInstance().getProcessService(); + versioningService = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(VersioningService.class.getName(), VersioningService.class); // Temporarily disabled claimedTaskService = XmlWorkflowServiceFactory.getInstance().getClaimedTaskService(); @@ -172,6 +176,7 @@ public abstract class AbstractBuilder { relationshipTypeService = null; entityTypeService = null; processService = null; + versioningService = null; } 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 5897f73944..69c3d117b3 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 @@ -44,6 +44,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; @LinkRest( name = ItemRest.THUMBNAIL, method = "getThumbnail" + ), + @LinkRest( + name = ItemRest.VERSION_HISTORY, + method = "getVersionHistory" ) }) public class ItemRest extends DSpaceObjectRest { @@ -58,6 +62,7 @@ public class ItemRest extends DSpaceObjectRest { public static final String VERSION = "version"; public static final String TEMPLATE_ITEM_OF = "templateItemOf"; public static final String THUMBNAIL = "thumbnail"; + public static final String VERSION_HISTORY = "versionhistory"; private boolean inArchive = false; private boolean discoverable = false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java new file mode 100644 index 0000000000..890ce36431 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java @@ -0,0 +1,64 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; +import java.sql.SQLException; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.VersionHistoryRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; +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 "versionhistory" subresource of an individual item. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.VERSION_HISTORY) +public class ItemVersionHistoryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private ItemService itemService; + + @Autowired + private VersionHistoryService versionHistoryService; + + @PreAuthorize("hasAuthority('ADMIN')") + public VersionHistoryRest getVersionHistory(@Nullable HttpServletRequest request, + UUID itemId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Item item = itemService.find(context, itemId); + if (item == null) { + throw new ResourceNotFoundException("No such item: " + itemId); + } + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + if (Objects.isNull(versionHistory)) { + throw new ResourceNotFoundException("No such VersionHistory for item: " + itemId); + } + return converter.toRest(versionHistory, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file 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 a263396cd6..17193910fa 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 @@ -6,15 +6,33 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.repository; - import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.NotFoundException; +import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.Logger; +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.VersionRest; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.handler.service.UriListHandlerService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -37,6 +55,15 @@ public class VersionRestRepository extends DSpaceRestRepository stringList) + throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { + + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + String summary = req.getParameter("summary"); + Item item = uriListHandlerService.handle(context, req, stringList, Item.class); + if (Objects.isNull(item)) { + throw new UnprocessableEntityException("The given URI list could not be properly parsed to one result"); + } + Version version = null; + if (StringUtils.isNotBlank(summary)) { + version = versioningService.createNewVersion(context, item, summary); + } else { + version = versioningService.createNewVersion(context, item); + } + return converter.toRest(version, utils.obtainProjection()); + } + + @PreAuthorize("hasAuthority('ADMIN')") + @SearchRestMethod(name = "findByItem") + public VersionRest findByItem(@Parameter(value = "itemUuid", required = true) UUID itemUuid) { + Context context = obtainContext(); + Version version = null; + try { + Item item = itemService.find(context, itemUuid); + if (Objects.nonNull(item)) { + version = versioningService.getVersion(context, item); + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return Objects.nonNull(version) ? converter.toRest(version, utils.obtainProjection()) : null; + } + + @PreAuthorize("hasAuthority('ADMIN')") + @SearchRestMethod(name = "findByHistory") + public Page findByHistory(@Parameter(value = "historyId", required = true) Integer historyId, + Pageable pageable) { + Context context = obtainContext(); + List versions = new LinkedList();; + int total = 0; + try { + VersionHistory versionHistory = versionHistoryService.find(context, historyId); + if (Objects.isNull(versionHistory)) { + throw new DSpaceBadRequestException( + "This given id:" + historyId + " does not resolve to a VersionHistory"); + } + versions = versioningService.getVersionsByHistory(context, versionHistory, + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getPageSize())); + total = versioningService.countVersionsByHistory(context, versionHistory); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return converter.toRestPage(versions, pageable, total, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, + Integer versionId, Patch patch) throws AuthorizeException, SQLException { + for (Operation operation : patch.getOperations()) { + if (!operation.getPath().equals("/summary")) { + throw new UnprocessableEntityException("The provided property:" + + operation.getPath() + " is not supported!"); + } + Version version = versioningService.getVersion(context, versionId); + if (Objects.isNull(version)) { + throw new NotFoundException("This given id:" + versionId + " does not resolve to a Version"); + } + switch (operation.getOp()) { + case "remove": + version.setSummary(StringUtils.EMPTY); + break; + case "add": + if (StringUtils.isNotBlank(version.getSummary())) { + throw new DSpaceBadRequestException("The 'summary' property is setted with value:" + + version.getSummary() + ", it is not possible to add new value"); + } + version.setSummary(operation.getValue().toString()); + break; + case "replace": + version.setSummary(operation.getValue().toString()); + break; + default: throw new UnprocessableEntityException("Provided operation:" + + operation.getOp() + " is not supported"); + } + } + } + @Override public Page findAll(Context context, Pageable pageable) { throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java index e188b04950..f19b247dd8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java @@ -62,8 +62,9 @@ public class VersionsLinkRepository extends AbstractDSpaceRestRepository throw new ResourceNotFoundException("The versionHistory with ID: " + versionHistoryId + " couldn't be found"); } - List versions = versioningService.getVersionsByHistory(context, versionHistory); Pageable pageable = optionalPageable != null ? optionalPageable : PageRequest.of(0, 20); + List versions = versioningService.getVersionsByHistory(context, versionHistory, + Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); return converter.toRestPage(versions, pageable, projection); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java new file mode 100644 index 0000000000..3adc8b9cfd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java @@ -0,0 +1,77 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.handler; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component +public class ExternalSourceItemUriListHandler extends ExternalSourceEntryItemUriListHandler { + + @Autowired + private ItemService itemService; + + @Override + @SuppressWarnings("rawtypes") + public boolean supports(List uriList, String method,Class clazz) { + if (clazz != Item.class) { + return false; + } + return true; + } + + @Override + public Item handle(Context context, HttpServletRequest request, List uriList) + throws SQLException, AuthorizeException { + return getObjectFromUriList(context, uriList); + } + + @Override + public boolean validate(Context context, HttpServletRequest request, List uriList) + throws AuthorizeException { + if (uriList.size() > 1) { + return false; + } + return true; + } + + + private Item getObjectFromUriList(Context context, List uriList) { + Item item = null; + String url = uriList.get(0); + Pattern pattern = Pattern.compile("\\/api\\/core\\/items\\/(.*)"); + Matcher matcher = pattern.matcher(url); + if (!matcher.find()) { + throw new DSpaceBadRequestException("The uri: " + url + " doesn't resolve to an item"); + } + String id = matcher.group(1); + UUID itemId = UUID.fromString(id); + try { + item = itemService.find(context, itemId); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return item; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 1c47825a21..818e55b656 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -62,6 +62,7 @@ import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.builder.VersionBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; @@ -77,6 +78,9 @@ import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; import org.dspace.workflow.WorkflowItem; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -89,6 +93,9 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private CollectionService collectionService; + @Autowired + private VersionHistoryService versionHistoryService; + private Item publication1; private Item author1; private Item author2; @@ -4082,4 +4089,133 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isNoContent()); } + @Test + public void versionHistoryForItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10) + .build(); + Version v98 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 11) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/items/" + item2.getID() + "/versionhistory")) + .andExpect(content().contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", is(versionHistory.getID())), + hasJsonPath("$.type", is("versionhistory")) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versionhistories/" + versionHistory.getID()) + ))) + .andExpect(jsonPath("$._links.versions.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versionhistories/" + versionHistory.getID() + "/versions") + ))); + } + + @Test + public void versionHistoryForItemNotFoundTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/items/" + item.getID() + "/versionhistory")) + .andExpect(status().isNotFound()); + } + + @Test + public void versionHistoryForItemUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/items/" + item.getID() + "/versionhistory")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void versionHistoryForItemForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10) + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/versionhistory")) + .andExpect(status().isForbidden()); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java index e280bffdef..fb8b44fa17 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java @@ -374,7 +374,7 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest private void cleanupVersion(int id) throws SQLException { Version version = versioningService.getVersion(context, id); - versioningService.removeVersion(context, version); + versioningService.delete(context, version); } From 69e36e7037ce47df81e0a9cb5bc55af92b8a6f69 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 19 Aug 2021 15:55:31 +0200 Subject: [PATCH 0160/1254] implemented linked entities for versionhistory end point --- .../org/dspace/builder/VersionBuilder.java | 109 +++ .../app/rest/model/VersionHistoryRest.java | 17 +- ...onHistoryCurrentVersionLinkRepository.java | 61 ++ ...rsionHistoryLastVersionLinkRepository.java | 61 ++ ...ionHistoryOldestVersionLinkRepository.java | 61 ++ .../repository/VersionRestRepository.java | 15 + .../repository/VersionsLinkRepository.java | 7 +- .../rest/VersionHistoryRestRepositoryIT.java | 460 +++++++++- .../app/rest/VersionRestRepositoryIT.java | 792 +++++++++++++++++- 9 files changed, 1561 insertions(+), 22 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryCurrentVersionLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLastVersionLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryOldestVersionLinkRepository.java diff --git a/dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java b/dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java new file mode 100644 index 0000000000..4313c585e1 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java @@ -0,0 +1,109 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.Date; +import java.util.Objects; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersioningService; + +/** + * Builder to construct Version objects + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class VersionBuilder extends AbstractBuilder { + + private static final Logger log = Logger.getLogger(VersionBuilder.class); + + private Version version; + + protected VersionBuilder(Context context) { + super(context); + } + + public static VersionBuilder createVersion(Context context, Item item, String summary) { + VersionBuilder builder = new VersionBuilder(context); + return builder.create(context, item, summary, null, 0); + } + + public static VersionBuilder createVersionWithVersionHistory(Context context, + Item item, String summary, VersionHistory versionHistory, int versionNumber) { + VersionBuilder builder = new VersionBuilder(context); + return builder.create(context, item, summary, versionHistory, versionNumber); + } + + private VersionBuilder create(Context context, Item item, String summary, + VersionHistory versionHistory, int versionNumber) { + try { + this.context = context; + if (Objects.nonNull(versionHistory)) { + this.version = getService().createNewVersion(context, versionHistory, item, summary, + new Date(), versionNumber); + } else { + if (StringUtils.isNotBlank(summary)) { + this.version = getService().createNewVersion(context, item, summary); + } else { + this.version = getService().createNewVersion(context, item); + } + } + } catch (Exception e) { + log.error("Error in VersionBuilder.create(..), error: ", e); + } + return this; + } + + @Override + public Version build() throws SQLException, AuthorizeException { + try { + getService().update(context, version); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + log.error("Error in VersionBuilder.build(), error: ", e); + } + return version; + } + + @Override + public void delete(Context context, Version version) throws Exception { + if (Objects.nonNull(version)) { + getService().delete(context, version); + } + } + + @Override + protected VersioningService getService() { + return versioningService; + } + + @Override + public void cleanup() throws Exception { + delete(version); + } + + public void delete(Version version) throws Exception { + try (Context context = new Context()) { + context.turnOffAuthorisationSystem(); + Version attachedTab = context.reloadEntity(version); + if (attachedTab != null) { + getService().delete(context, attachedTab); + } + context.complete(); + } + indexingService.commit(); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java index eddca157a4..a6f364874b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java @@ -16,7 +16,19 @@ import org.dspace.app.rest.RestResourceController; @LinkRest( name = VersionHistoryRest.VERSIONS, method = "getVersions" - ) + ), + @LinkRest( + name = VersionHistoryRest.OLDEST_VERSION, + method = "getOldestVersion" + ), + @LinkRest( + name = VersionHistoryRest.CURRENT_VERSION, + method = "getCurrentVersion" + ), + @LinkRest( + name = VersionHistoryRest.LAST_VERSION, + method = "getLastVersion" + ) }) public class VersionHistoryRest extends BaseObjectRest { @@ -25,6 +37,9 @@ public class VersionHistoryRest extends BaseObjectRest { public static final String NAME = "versionhistory"; public static final String CATEGORY = RestAddressableModel.VERSIONING; public static final String VERSIONS = "versions"; + public static final String OLDEST_VERSION = "oldestversion"; + public static final String CURRENT_VERSION = "currentversion"; + public static final String LAST_VERSION = "lastversion"; @Override public String getCategory() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryCurrentVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryCurrentVersionLinkRepository.java new file mode 100644 index 0000000000..537d5bd5aa --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryCurrentVersionLinkRepository.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 java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.VersionHistoryRest; +import org.dspace.app.rest.model.VersionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; +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; + +/** + * This is the Repository that takes care of the retrieval of the current Version object + * for a given {@link VersionHistory} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.CURRENT_VERSION) +public class VersionHistoryCurrentVersionLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private VersionHistoryService versionHistoryService; + + @PreAuthorize("hasAuthority('ADMIN')") + public VersionRest getCurrentVersion(@Nullable HttpServletRequest request, + Integer versionHistoryId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException { + Context context = obtainContext(); + if (Objects.isNull(versionHistoryId) || versionHistoryId < 0) { + throw new DSpaceBadRequestException("Provied id is not correct!"); + } + VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); + if (Objects.isNull(versionHistory)) { + throw new ResourceNotFoundException("No such versio found"); + } + Version currentVersion = versionHistoryService.getLatestVersion(context, versionHistory); + if (Objects.isNull(currentVersion)) { + throw new ResourceNotFoundException("No such version for versionhistory with id:" + versionHistoryId); + } + return converter.toRest(currentVersion, projection); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLastVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLastVersionLinkRepository.java new file mode 100644 index 0000000000..3ee62367d5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLastVersionLinkRepository.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 java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.VersionHistoryRest; +import org.dspace.app.rest.model.VersionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; +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; + +/** + * This is the Repository that retrieve the most recent version in the history + * that could live eventually in the workspace or workflow. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.LAST_VERSION) +public class VersionHistoryLastVersionLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private VersionHistoryService versionHistoryService; + + @PreAuthorize("hasAuthority('ADMIN')") + public VersionRest getLastVersion(@Nullable HttpServletRequest request, + Integer versionHistoryId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException { + Context context = obtainContext(); + if (Objects.isNull(versionHistoryId) || versionHistoryId < 0) { + throw new DSpaceBadRequestException("Provied id is not correct!"); + } + VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); + if (Objects.isNull(versionHistory)) { + throw new ResourceNotFoundException("No such versio found"); + } + Version oldestVersion = versionHistoryService.getLatestVersion(context, versionHistory); + if (Objects.isNull(oldestVersion)) { + throw new ResourceNotFoundException("No such version for versionhistory with id:" + versionHistoryId); + } + return converter.toRest(oldestVersion, projection); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryOldestVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryOldestVersionLinkRepository.java new file mode 100644 index 0000000000..7b4277be25 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryOldestVersionLinkRepository.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 java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.VersionHistoryRest; +import org.dspace.app.rest.model.VersionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; +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; + +/** + * This is the Repository that takes care of the retrieval of the oldest Version object + * for a given {@link VersionHistory} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.OLDEST_VERSION) +public class VersionHistoryOldestVersionLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private VersionHistoryService versionHistoryService; + + @PreAuthorize("hasAuthority('ADMIN')") + public VersionRest getOldestVersion(@Nullable HttpServletRequest request, + Integer versionHistoryId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException { + Context context = obtainContext(); + if (Objects.isNull(versionHistoryId) || versionHistoryId < 0) { + throw new DSpaceBadRequestException("Provied id is not correct!"); + } + VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); + if (Objects.isNull(versionHistory)) { + throw new ResourceNotFoundException("No such versio found"); + } + Version oldestVersion = versionHistoryService.getFirstVersion(context, versionHistory); + if (Objects.isNull(oldestVersion)) { + throw new ResourceNotFoundException("No such version for versionhistory with id:" + versionHistoryId); + } + return converter.toRest(oldestVersion, projection); + } + +} \ No newline at end of file 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 17193910fa..a2850582ee 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 @@ -29,11 +29,13 @@ import org.dspace.app.rest.repository.handler.service.UriListHandlerService; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; +import org.dspace.workflow.WorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -43,6 +45,8 @@ import org.springframework.stereotype.Component; /** * This is the Repository that takes care of the operations on the {@link VersionRest} objects + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ @Component(VersionRest.CATEGORY + "." + VersionRest.NAME) public class VersionRestRepository extends DSpaceRestRepository { @@ -64,6 +68,13 @@ public class VersionRestRepository extends DSpaceRestRepository versions = versioningService.getVersionsByHistory(context, versionHistory, - Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - return converter.toRestPage(versions, pageable, projection); + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getPageSize())); + total = versioningService.countVersionsByHistory(context, versionHistory); + return converter.toRestPage(versions, pageable, total, projection); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java index 60fdb11c97..61af01a011 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java @@ -7,9 +7,9 @@ */ package org.dspace.app.rest; -import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; +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; @@ -24,19 +24,29 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; +import org.dspace.builder.VersionBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +/** + * Integration test class for the VersionHistory endpoint. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegrationTest { @@ -66,7 +76,6 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio .withName("Sub Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); //2. Three public items that are readable by Anonymous with different subjects item = ItemBuilder.createItem(context, col1) @@ -75,7 +84,7 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio .withAuthor("Smith, Donald").withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); - version = versioningService.createNewVersion(context, versionHistory, item, "test", new Date(), 0); + version = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 0).build(); context.restoreAuthSystemState(); } @@ -138,8 +147,8 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio context.turnOffAuthorisationSystem(); Version version = versionHistoryService.getFirstVersion(context, versionHistory); - Version secondVersion = versioningService - .createNewVersion(context, versionHistory, item, "test", new Date(), 0); + Version secondVersion = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 3) + .build(); context.restoreAuthSystemState(); getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions") @@ -158,5 +167,446 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio } + @Test + public void findOldestVersionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v7 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7) + .build(); + VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98) + .build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v7)))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v7.getID()) + ))) + .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v7.getID() + "/versionhistory" + )))) + .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v7.getID() + "/item") + ))); + } + + @Test + public void findOldestVersionNotFoundTest() throws Exception { + context.turnOffAuthorisationSystem(); + VersionHistory versionHistory = versionHistoryService.create(context); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) + .andExpect(status().isNotFound()); + } + + @Test + public void findOldestVersionForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7).build(); + VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98).build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) + .andExpect(status().isForbidden()); + } + + @Test + public void findOldestVersionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7).build(); + VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98).build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + getClient().perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOldestVersionNTest() throws Exception { + Integer id = Integer.MAX_VALUE; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) + .andExpect(status().isNotFound()); + } + + @Test + public void findOldestVersionBadRequestTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + null + "/oldestversion")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findOldestVersionWithNegativVersionHistoryIdTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + -1 + "/oldestversion")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findCurrentVersionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7) + .build(); + Version v98 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98) + .build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v98)))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v98.getID()) + ))) + .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v98.getID() + "/versionhistory" + )))) + .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v98.getID() + "/item") + ))); + } + + @Test + public void findCurrentVersionNotFoundTest() throws Exception { + context.turnOffAuthorisationSystem(); + VersionHistory versionHistory = versionHistoryService.create(context); + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) + .andExpect(status().isNotFound()); + } + + @Test + public void findCurrentVersionForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7).build(); + VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98).build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) + .andExpect(status().isForbidden()); + } + + @Test + public void findCurrentVersionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7).build(); + VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98).build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + getClient().perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findCurrentVersionWithVersionHistoryNotFoundTest() throws Exception { + Integer id = Integer.MAX_VALUE; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) + .andExpect(status().isNotFound()); + } + + @Test + public void findCurrentVersionBadRequestTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + null + "/currentversion")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findCurrentVersionWithNegativVersionHistoryIdTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + -1 + "/currentversion")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findLastVersionWorkspaceItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 6).build(); + Version v10 = VersionBuilder.createVersionWithVersionHistory(context, witem.getItem(), + "test", versionHistory, 10).build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/lastversion")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v10)))); + } + + @Test + public void findLastVersionItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v6 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 6).build(); + VersionBuilder.createVersionWithVersionHistory(context, item2, "test", versionHistory, 3).build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/lastversion")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v6)))); + } + + @Test + public void findLastVersionWorkflowItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + XmlWorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, col) + .withTitle("Workflow Item") + .withIssueDate("2021-05-21") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 6).build(); + Version v10 = VersionBuilder.createVersionWithVersionHistory(context, workflowItem.getItem(), + "test", versionHistory, 10).build(); + + context.restoreAuthSystemState(); + + Integer id = versionHistory.getID(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/lastversion")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v10)))); + } + + @Test + public void findLastVersionNotFoundTest() throws Exception { + Integer id = Integer.MAX_VALUE; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/lastversion")) + .andExpect(status().isNotFound()); + } + + @Test + public void findLastVersionBadRequestTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + null + "/lastversion")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findLastVersionWithNegativVersionHistoryIdTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + -1 + "/lastversion")) + .andExpect(status().isBadRequest()); + } } 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 a6087c58c2..da8dd0d753 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 @@ -6,42 +6,63 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest; - +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.emptyString; +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.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.VersionMatcher; +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.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; +import org.dspace.builder.VersionBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.InstallItemService; -import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; -import org.dspace.versioning.service.VersioningService; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.RestMediaTypes; +import org.springframework.http.MediaType; +/** + * Integration test class for the version endpoint. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { - Item item; - Version version; + private Item item; - @Autowired - private ItemService itemService; - - @Autowired - private VersioningService versioningService; + private Version version; @Autowired private ConfigurationService configurationService; @@ -52,8 +73,11 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private WorkspaceItemService workspaceItemService; + @Autowired + private VersionHistoryService versionHistoryService; + @Before - public void setup() { + public void setup() throws SQLException, AuthorizeException { context.turnOffAuthorisationSystem(); //** GIVEN ** @@ -65,7 +89,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withName("Sub Community") .build(); Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); //2. Three public items that are readable by Anonymous with different subjects item = ItemBuilder.createItem(context, col1) @@ -74,7 +97,8 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withAuthor("Smith, Donald").withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); - version = versioningService.createNewVersion(context, item); + + version = VersionBuilder.createVersion(context, item, "Fixing some typos").build(); context.restoreAuthSystemState(); } @@ -202,4 +226,744 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(adminToken).perform(get("/api/versioning/versions/" + ((version.getID() + 5) * 57) + "/item")) .andExpect(status().isNotFound()); } -} + + @Test + public void deleteVersionItemAdminTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(delete("/api/versioning/versions/" + version.getID())) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteVersionItemUnauthorizedTest() throws Exception { + getClient().perform(delete("/api/versioning/versions/" + version.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteVersionItemForbiddenTest() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(delete("/api/versioning/versions/" + version.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteVersionItemNotFoundTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(delete("/api/versioning/versions/" + Integer.MAX_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + public void createFirstVersionItemTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).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(3)), + hasJsonPath("$.summary", is("test summary!")), + hasJsonPath("$.submitterName", is("first (admin) last (admin)")), + hasJsonPath("$.type", is("version")) + ))); + } + + @Test + public void createFirstVersionItemBadRequestTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/test/" + UUID.randomUUID())) + .andExpect(status().isBadRequest()); + } + + @Test + public void createFirstVersionItemForbiddenTest() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).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().isForbidden()); + } + + @Test + public void createFirstVersionItemUnauthorizedTest() throws Exception { + getClient().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().isUnauthorized()); + } + + @Test + public void createVersionFromVersionedItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10).build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).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(11)), + hasJsonPath("$.summary", is("test summary.")), + hasJsonPath("$.submitterName", is("first (admin) last (admin)")), + hasJsonPath("$.type", is("version")) + ))); + } + + @Test + public void createVersionByPreviousVersionRespectCurrentVersionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-03-20") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withIssueDate("2021-03-20") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 1).build(); + VersionBuilder.createVersionWithVersionHistory(context, item2, "tes2", versionHistory, 2).build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/versioning/versions") + .param("summary", "check first version") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + item.getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(3)), + hasJsonPath("$.summary", is("check first version")), + hasJsonPath("$.submitterName", is("first (admin) last (admin)")), + hasJsonPath("$.type", is("version")) + ))); + } + + @Test + public void createVersionFromWorkflowItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withWorkflowGroup(1, admin) + .withName("Collection test").build(); + + XmlWorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, col) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/versioning/versions") + .param("summary", "fix workspaceitem") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + workflowItem.getItem().getID())) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createVersionFromWorkspaceItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/versioning/versions") + .param("summary", "fix workspaceitem") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + witem.getItem().getID())) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void findByItemAdminTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versions/search/findByItem") + .param("itemUuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(1)), + hasJsonPath("$.summary", emptyString()), + hasJsonPath("$.submitterName", is("first last")), + hasJsonPath("$.type", is("version")) + ))); + } + + @Test + public void findByItemVersionUnauthorizedTest() throws Exception { + getClient().perform(get("/api/versioning/versions/search/findByItem") + .param("itemUuid", item.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByItemNotYetVersionedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Sample 1 collection") + .withSubmitterGroup(eperson).build(); + + Item publicItem = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withAuthor("Smith, Maria") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versions/search/findByItem") + .param("itemUuid", publicItem.getID().toString())) + .andExpect(status().isNoContent()); + } + + @Test + public void findByItemForbiddenTest() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/versioning/versions/search/findByItem") + .param("itemUuid", item.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void findByItemBadRequestTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + + // missing to provid item uuid + getClient(adminToken).perform(get("/api/versioning/versions/search/findByItem")) + .andExpect(status().isBadRequest()); + + // provided wrong uuid + getClient(adminToken).perform(get("/api/versioning/versions/search/findByItem") + .param("itemUuid", "wrong id")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findByHistoryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) + .build(); + Version v98 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98) + .build(); + Version v99 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 3", versionHistory, 0) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") + .param("historyId", versionHistory.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", Matchers.contains( + VersionMatcher.matchEntry(v99), + VersionMatcher.matchEntry(v98), + VersionMatcher.matchEntry(v77) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/search/findByHistory?historyId=" + versionHistory.getID())))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findByHistoryPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item item2 = ItemBuilder.createItem(context, col) + .withTitle("Public test item 2") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) + .build(); + Version v98 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98) + .build(); + Version v99 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 3", versionHistory, 0) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") + .param("size", "1") + .param("historyId", versionHistory.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", Matchers.contains( + VersionMatcher.matchEntry(v99) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=0"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=1"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=2"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") + .param("page", "1") + .param("size", "1") + .param("historyId", versionHistory.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", Matchers.contains( + VersionMatcher.matchEntry(v98) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=1"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=0"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=0"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=2"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=2"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(1))); + + getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") + .param("page", "2") + .param("size", "1") + .param("historyId", versionHistory.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", Matchers.contains( + VersionMatcher.matchEntry(v77) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=2"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=0"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=1"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("page=2"), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(2))); + } + + @Test + public void findByHistoryPaginationEmptyResponseTest() throws Exception { + context.turnOffAuthorisationSystem(); + VersionHistory versionHistory = versionHistoryService.create(context); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") + .param("size", "1") + .param("historyId", versionHistory.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions").doesNotExist()) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("api/versioning/versions/search/findByHistory?"), + Matchers.containsString("historyId=" + versionHistory.getID()), + Matchers.containsString("size=1") + ))) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(0))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findByHistoryPaginationXXXTest() throws Exception { + Integer id = Integer.MAX_VALUE; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") + .param("size", "1") + .param("historyId", id.toString())) + .andExpect(status().isBadRequest()); + } + + @Test + public void patchReplaceSummaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) + .build(); + + context.restoreAuthSystemState(); + + String newSummary = "New Summary"; + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/summary", newSummary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.summary", is(newSummary)), + hasJsonPath("$.submitterName", is("first last")), + hasJsonPath("$.type", is("version")) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID()) + ))) + .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID() + "/versionhistory" + )))) + .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID() + "/item") + ))); + + } + + @Test + public void patchRemoveSummaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) + .build(); + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + RemoveOperation replaceOperation = new RemoveOperation("/summary"); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.summary", emptyString()), + hasJsonPath("$.submitterName", is("first last")), + hasJsonPath("$.type", is("version")) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID()) + ))) + .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID() + "/versionhistory" + )))) + .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID() + "/item") + ))); + + } + + @Test + public void patchAddSummaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "", versionHistory, 77) + .build(); + + context.restoreAuthSystemState(); + + String summary = "First Summary!"; + List ops = new ArrayList(); + AddOperation replaceOperation = new AddOperation("/summary", summary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.summary", is(summary)), + hasJsonPath("$.submitterName", is("first last")), + hasJsonPath("$.type", is("version")) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID()) + ))) + .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID() + "/versionhistory" + )))) + .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v77.getID() + "/item") + ))); + } + + @Test + public void patchAddSummaryBadRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) + .build(); + + context.restoreAuthSystemState(); + + String summary = "First Summary!"; + List ops = new ArrayList(); + AddOperation replaceOperation = new AddOperation("/summary", summary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void patchWrongPathUnprocessableEntityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) + .build(); + + context.restoreAuthSystemState(); + + String summary = "First Summary!"; + List ops = new ArrayList(); + AddOperation replaceOperation = new AddOperation("/wrongPath", summary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + } + +} \ No newline at end of file From fe4332e2ea860b0aab4f0ed806ca66e316316049 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 19 Aug 2021 17:55:55 +0200 Subject: [PATCH 0161/1254] fix failed tests --- .../rest/repository/ItemVersionHistoryLinkRepository.java | 7 ++----- .../java/org/dspace/app/rest/ItemRestRepositoryIT.java | 2 +- .../test/java/org/dspace/app/rest/matcher/ItemMatcher.java | 3 ++- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java index 890ce36431..22193a1a3f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java @@ -51,11 +51,8 @@ public class ItemVersionHistoryLinkRepository extends AbstractDSpaceRestReposito if (item == null) { throw new ResourceNotFoundException("No such item: " + itemId); } - VersionHistory versionHistory = versionHistoryService.findByItem(context, item); - if (Objects.isNull(versionHistory)) { - throw new ResourceNotFoundException("No such VersionHistory for item: " + itemId); - } - return converter.toRest(versionHistory, projection); + VersionHistory vh = versionHistoryService.findByItem(context, item); + return Objects.nonNull(vh) ? converter.toRest(vh, projection) : null; } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 818e55b656..8dd598227d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -4159,7 +4159,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(get("/api/core/items/" + item.getID() + "/versionhistory")) - .andExpect(status().isNotFound()); + .andExpect(status().isNoContent()); } @Test 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 389b8bf492..27934b36f5 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 @@ -56,7 +56,8 @@ public class ItemMatcher { "version", "relationships[]", "templateItemOf", - "thumbnail" + "thumbnail", + "versionhistory" ); } From 4da733baa7f2495de64177cad5d184c8666eaef4 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 20 Aug 2021 14:59:00 -0400 Subject: [PATCH 0162/1254] Remove useless pagination arguments. #3369 --- .../app/requestitem/dao/impl/RequestItemDAOImpl.java | 2 +- .../checker/dao/impl/ChecksumResultDAOImpl.java | 4 ++-- .../content/dao/impl/BitstreamFormatDAOImpl.java | 4 ++-- .../dspace/content/dao/impl/CollectionDAOImpl.java | 6 +++--- .../dspace/content/dao/impl/EntityTypeDAOImpl.java | 11 ++++++++++- .../content/dao/impl/RelationshipTypeDAOImpl.java | 11 ++++++++++- .../java/org/dspace/content/dao/impl/SiteDAOImpl.java | 4 ++-- .../dspace/content/dao/impl/WorkspaceItemDAOImpl.java | 4 ++-- .../java/org/dspace/core/AbstractHibernateDAO.java | 8 +++----- .../java/org/dspace/core/AbstractHibernateDSODAO.java | 2 +- .../org/dspace/eperson/dao/impl/EPersonDAOImpl.java | 6 +++--- .../eperson/dao/impl/Group2GroupCacheDAOImpl.java | 4 ++-- .../eperson/dao/impl/RegistrationDataDAOImpl.java | 6 +++--- .../org/dspace/identifier/dao/impl/DOIDAOImpl.java | 4 ++-- .../storedcomponents/dao/impl/ClaimedTaskDAOImpl.java | 6 +++--- .../dao/impl/CollectionRoleDAOImpl.java | 4 ++-- .../dao/impl/InProgressUserDAOImpl.java | 4 ++-- .../storedcomponents/dao/impl/PoolTaskDAOImpl.java | 6 +++--- .../dao/impl/XmlWorkflowItemDAOImpl.java | 4 ++-- 19 files changed, 58 insertions(+), 42 deletions(-) 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 351f40ae13..92d8051939 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 @@ -37,7 +37,7 @@ public class RequestItemDAOImpl extends AbstractHibernateDAO implem Root requestItemRoot = criteriaQuery.from(RequestItem.class); criteriaQuery.select(requestItemRoot); criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.token), token)); - return uniqueResult(context, criteriaQuery, false, RequestItem.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, RequestItem.class); } diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumResultDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumResultDAOImpl.java index a82b904b28..7552c6d5bb 100644 --- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumResultDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/ChecksumResultDAOImpl.java @@ -21,7 +21,7 @@ import org.dspace.core.Context; /** * Hibernate implementation of the Database Access Object interface class for the ChecksumResult object. - * This class is responsible for all database calls for the ChecksumResult object and is autowired by spring + * This class is responsible for all database calls for the ChecksumResult object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -39,6 +39,6 @@ public class ChecksumResultDAOImpl extends AbstractHibernateDAO Root checksumResultRoot = criteriaQuery.from(ChecksumResult.class); criteriaQuery.select(checksumResultRoot); criteriaQuery.where(criteriaBuilder.equal(checksumResultRoot.get(ChecksumResult_.resultCode), code)); - return uniqueResult(context, criteriaQuery, false, ChecksumResult.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, ChecksumResult.class); } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamFormatDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamFormatDAOImpl.java index 0824c5c343..4d9283bbec 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamFormatDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamFormatDAOImpl.java @@ -23,7 +23,7 @@ import org.dspace.core.Context; /** * Hibernate implementation of the Database Access Object interface class for the BitstreamFormat object. - * This class is responsible for all database calls for the BitstreamFormat object and is autowired by spring + * This class is responsible for all database calls for the BitstreamFormat object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -86,7 +86,7 @@ public class BitstreamFormatDAOImpl extends AbstractHibernateDAO bitstreamFormatRoot = criteriaQuery.from(BitstreamFormat.class); criteriaQuery.select(bitstreamFormatRoot); criteriaQuery.where(criteriaBuilder.equal(bitstreamFormatRoot.get(BitstreamFormat_.shortDescription), desc)); - return uniqueResult(context, criteriaQuery, false, BitstreamFormat.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, BitstreamFormat.class); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index b08a57cbe1..87c7c3155d 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -34,7 +34,7 @@ import org.dspace.eperson.Group; /** * Hibernate implementation of the Database Access Object interface class for the Collection object. - * This class is responsible for all database calls for the Collection object and is autowired by spring + * This class is responsible for all database calls for the Collection object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -95,7 +95,7 @@ public class CollectionDAOImpl extends AbstractHibernateDSODAO imple Root collectionRoot = criteriaQuery.from(Collection.class); criteriaQuery.select(collectionRoot); criteriaQuery.where(criteriaBuilder.equal(collectionRoot.get(Collection_.template), item)); - return uniqueResult(context, criteriaQuery, false, Collection.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, Collection.class); } @Override @@ -119,7 +119,7 @@ public class CollectionDAOImpl extends AbstractHibernateDSODAO imple CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Collection.class); Root collectionRoot = criteriaQuery.from(Collection.class); Join join = collectionRoot.join("resourcePolicies"); - List orPredicates = new LinkedList(); + List orPredicates = new LinkedList<>(); for (Integer action : actions) { orPredicates.add(criteriaBuilder.equal(join.get(ResourcePolicy_.actionId), action)); } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java index b9c48c6711..f7a4dcfdf3 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java @@ -18,6 +18,15 @@ import org.dspace.content.dao.EntityTypeDAO; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; +/** + * Hibernate implementation of the Database Access Object interface class for + * the EntityType object. + * This class is responsible for all database calls for the EntityType object + * and is autowired by Spring. + * This class should never be accessed directly. + * + * @author kevinvandevelde at atmire.com + */ public class EntityTypeDAOImpl extends AbstractHibernateDAO implements EntityTypeDAO { @Override @@ -28,6 +37,6 @@ public class EntityTypeDAOImpl extends AbstractHibernateDAO implemen criteriaQuery.select(entityTypeRoot); criteriaQuery.where(criteriaBuilder.equal(criteriaBuilder.upper(entityTypeRoot.get(EntityType_.label)), entityType.toUpperCase())); - return uniqueResult(context, criteriaQuery, true, EntityType.class, -1, -1); + return uniqueResult(context, criteriaQuery, true, EntityType.class); } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java index 96d4bf68fb..2192634793 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java @@ -20,6 +20,15 @@ import org.dspace.content.dao.RelationshipTypeDAO; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; +/** + * Hibernate implementation of the Database Access Object interface class for + * the RelationshipType object. + * This class is responsible for all database calls for the RelationshipType + * object and is autowired by Spring. + * This class should never be accessed directly. + * + * @author kevinvandevelde at atmire.com + */ public class RelationshipTypeDAOImpl extends AbstractHibernateDAO implements RelationshipTypeDAO { @Override @@ -36,7 +45,7 @@ public class RelationshipTypeDAOImpl extends AbstractHibernateDAO implements SiteDAO { CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Site.class); Root siteRoot = criteriaQuery.from(Site.class); criteriaQuery.select(siteRoot); - return uniqueResult(context, criteriaQuery, true, Site.class, -1, -1); + return uniqueResult(context, criteriaQuery, true, Site.class); } } 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 8e5e803898..de1b9a5aea 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 @@ -31,7 +31,7 @@ import org.dspace.eperson.Group; /** * Hibernate implementation of the Database Access Object interface class for the WorkspaceItem object. - * This class is responsible for all database calls for the WorkspaceItem object and is autowired by spring + * This class is responsible for all database calls for the WorkspaceItem object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -81,7 +81,7 @@ public class WorkspaceItemDAOImpl extends AbstractHibernateDAO im Root workspaceItemRoot = criteriaQuery.from(WorkspaceItem.class); criteriaQuery.select(workspaceItemRoot); criteriaQuery.where(criteriaBuilder.equal(workspaceItemRoot.get(WorkspaceItem_.item), i)); - return uniqueResult(context, criteriaQuery, false, WorkspaceItem.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, WorkspaceItem.class); } @Override diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java index 9f97dce1ce..4aab9e7642 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java @@ -212,16 +212,14 @@ public abstract class AbstractHibernateDAO implements GenericDAO { * @param criteriaQuery JPA criteria * @param cacheable whether or not this query should be cacheable. * @param clazz type of object that should match the query. - * @param maxResults return at most this many results. - * @param offset skip this many leading results. * @return the single model object specified by the criteria, * or {@code null} if none match. * @throws java.sql.SQLException passed through. * @throws IllegalArgumentException if multiple objects match. */ - public T uniqueResult(Context context, CriteriaQuery criteriaQuery, boolean cacheable, Class clazz, - int maxResults, int offset) throws SQLException { - List list = list(context, criteriaQuery, cacheable, clazz, maxResults, offset); + public T uniqueResult(Context context, CriteriaQuery criteriaQuery, + boolean cacheable, Class clazz) throws SQLException { + List list = list(context, criteriaQuery, cacheable, clazz, -1, -1); if (CollectionUtils.isNotEmpty(list)) { if (list.size() == 1) { return list.get(0); diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java index 232431cac7..e6535f0941 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDSODAO.java @@ -48,7 +48,7 @@ public abstract class AbstractHibernateDSODAO extends Ab CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, clazz); Root root = criteriaQuery.from(clazz); criteriaQuery.where(criteriaBuilder.equal(root.get("legacyId"), legacyId)); - return uniqueResult(context, criteriaQuery, false, clazz, -1, -1); + return uniqueResult(context, criteriaQuery, false, clazz); } /** diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java index fd4c6f59d9..50547a5007 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/EPersonDAOImpl.java @@ -33,7 +33,7 @@ import org.dspace.eperson.dao.EPersonDAO; /** * Hibernate implementation of the Database Access Object interface class for the EPerson object. - * This class is responsible for all database calls for the EPerson object and is autowired by spring + * This class is responsible for all database calls for the EPerson object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -50,7 +50,7 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO implements Root ePersonRoot = criteriaQuery.from(EPerson.class); criteriaQuery.select(ePersonRoot); criteriaQuery.where(criteriaBuilder.equal(ePersonRoot.get(EPerson_.email), email.toLowerCase())); - return uniqueResult(context, criteriaQuery, true, EPerson.class, -1, -1); + return uniqueResult(context, criteriaQuery, true, EPerson.class); } @@ -61,7 +61,7 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO implements Root ePersonRoot = criteriaQuery.from(EPerson.class); criteriaQuery.select(ePersonRoot); criteriaQuery.where((criteriaBuilder.equal(ePersonRoot.get(EPerson_.netid), netid))); - return uniqueResult(context, criteriaQuery, true, EPerson.class, -1, -1); + return uniqueResult(context, criteriaQuery, true, EPerson.class); } @Override diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java index 717b41e8b9..83fb48aaf0 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/Group2GroupCacheDAOImpl.java @@ -25,7 +25,7 @@ import org.dspace.eperson.dao.Group2GroupCacheDAO; /** * Hibernate implementation of the Database Access Object interface class for the Group2GroupCache object. - * This class is responsible for all database calls for the Group2GroupCache object and is autowired by spring + * This class is responsible for all database calls for the Group2GroupCache object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -83,7 +83,7 @@ public class Group2GroupCacheDAOImpl extends AbstractHibernateDAO registrationDataRoot = criteriaQuery.from(RegistrationData.class); criteriaQuery.select(registrationDataRoot); criteriaQuery.where(criteriaBuilder.equal(registrationDataRoot.get(RegistrationData_.email), email)); - return uniqueResult(context, criteriaQuery, false, RegistrationData.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, RegistrationData.class); } @Override @@ -49,7 +49,7 @@ public class RegistrationDataDAOImpl extends AbstractHibernateDAO registrationDataRoot = criteriaQuery.from(RegistrationData.class); criteriaQuery.select(registrationDataRoot); criteriaQuery.where(criteriaBuilder.equal(registrationDataRoot.get(RegistrationData_.token), token)); - return uniqueResult(context, criteriaQuery, false, RegistrationData.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, RegistrationData.class); } @Override diff --git a/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java b/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java index 019e89c129..64e2952293 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java @@ -24,7 +24,7 @@ import org.dspace.identifier.dao.DOIDAO; /** * Hibernate implementation of the Database Access Object interface class for the DOI object. - * This class is responsible for all database calls for the DOI object and is autowired by spring + * This class is responsible for all database calls for the DOI object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -41,7 +41,7 @@ public class DOIDAOImpl extends AbstractHibernateDAO implements DOIDAO { Root doiRoot = criteriaQuery.from(DOI.class); criteriaQuery.select(doiRoot); criteriaQuery.where(criteriaBuilder.equal(doiRoot.get(DOI_.doi), doi)); - return uniqueResult(context, criteriaQuery, false, DOI.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, DOI.class); } @Override diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/ClaimedTaskDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/ClaimedTaskDAOImpl.java index bb5a167237..956a4648c5 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/ClaimedTaskDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/ClaimedTaskDAOImpl.java @@ -23,7 +23,7 @@ import org.dspace.xmlworkflow.storedcomponents.dao.ClaimedTaskDAO; /** * Hibernate implementation of the Database Access Object interface class for the ClaimedTask object. - * This class is responsible for all database calls for the ClaimedTask object and is autowired by spring + * This class is responsible for all database calls for the ClaimedTask object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -56,7 +56,7 @@ public class ClaimedTaskDAOImpl extends AbstractHibernateDAO implem criteriaBuilder.equal(claimedTaskRoot.get(ClaimedTask_.owner), ePerson) ) ); - return uniqueResult(context, criteriaQuery, false, ClaimedTask.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, ClaimedTask.class); } @@ -101,7 +101,7 @@ public class ClaimedTaskDAOImpl extends AbstractHibernateDAO implem criteriaBuilder.equal(claimedTaskRoot.get(ClaimedTask_.actionId), actionID) ) ); - return uniqueResult(context, criteriaQuery, false, ClaimedTask.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, ClaimedTask.class); } @Override diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/CollectionRoleDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/CollectionRoleDAOImpl.java index 429bf96471..b3cd32c74f 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/CollectionRoleDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/CollectionRoleDAOImpl.java @@ -24,7 +24,7 @@ import org.dspace.xmlworkflow.storedcomponents.dao.CollectionRoleDAO; /** * Hibernate implementation of the Database Access Object interface class for the CollectionRole object. - * This class is responsible for all database calls for the CollectionRole object and is autowired by spring + * This class is responsible for all database calls for the CollectionRole object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -66,7 +66,7 @@ public class CollectionRoleDAOImpl extends AbstractHibernateDAO criteriaBuilder.equal(collectionRoleRoot.get(CollectionRole_.roleId), role) ) ); - return uniqueResult(context, criteriaQuery, false, CollectionRole.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, CollectionRole.class); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/InProgressUserDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/InProgressUserDAOImpl.java index cdba1600a8..783d403c05 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/InProgressUserDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/InProgressUserDAOImpl.java @@ -23,7 +23,7 @@ import org.dspace.xmlworkflow.storedcomponents.dao.InProgressUserDAO; /** * Hibernate implementation of the Database Access Object interface class for the InProgressUser object. - * This class is responsible for all database calls for the InProgressUser object and is autowired by spring + * This class is responsible for all database calls for the InProgressUser object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -46,7 +46,7 @@ public class InProgressUserDAOImpl extends AbstractHibernateDAO criteriaBuilder.equal(inProgressUserRoot.get(InProgressUser_.ePerson), ePerson) ) ); - return uniqueResult(context, criteriaQuery, false, InProgressUser.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, InProgressUser.class); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/PoolTaskDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/PoolTaskDAOImpl.java index b38041da39..0857a325b5 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/PoolTaskDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/PoolTaskDAOImpl.java @@ -24,7 +24,7 @@ import org.dspace.xmlworkflow.storedcomponents.dao.PoolTaskDAO; /** * Hibernate implementation of the Database Access Object interface class for the PoolTask object. - * This class is responsible for all database calls for the PoolTask object and is autowired by spring + * This class is responsible for all database calls for the PoolTask object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -77,7 +77,7 @@ public class PoolTaskDAOImpl extends AbstractHibernateDAO implements P criteriaBuilder.equal(poolTaskRoot.get(PoolTask_.ePerson), ePerson) ) ); - return uniqueResult(context, criteriaQuery, false, PoolTask.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, PoolTask.class); } @Override @@ -92,6 +92,6 @@ public class PoolTaskDAOImpl extends AbstractHibernateDAO implements P criteriaBuilder.equal(poolTaskRoot.get(PoolTask_.group), group) ) ); - return uniqueResult(context, criteriaQuery, false, PoolTask.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, PoolTask.class); } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/XmlWorkflowItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/XmlWorkflowItemDAOImpl.java index 51728af7a4..659a2123d9 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/XmlWorkflowItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/storedcomponents/dao/impl/XmlWorkflowItemDAOImpl.java @@ -26,7 +26,7 @@ import org.dspace.xmlworkflow.storedcomponents.dao.XmlWorkflowItemDAO; /** * Hibernate implementation of the Database Access Object interface class for the XmlWorkflowItem object. - * This class is responsible for all database calls for the XmlWorkflowItem object and is autowired by spring + * This class is responsible for all database calls for the XmlWorkflowItem object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com @@ -132,6 +132,6 @@ public class XmlWorkflowItemDAOImpl extends AbstractHibernateDAO xmlWorkflowItemRoot = criteriaQuery.from(XmlWorkflowItem.class); criteriaQuery.select(xmlWorkflowItemRoot); criteriaQuery.where(criteriaBuilder.equal(xmlWorkflowItemRoot.get(XmlWorkflowItem_.item), item)); - return uniqueResult(context, criteriaQuery, false, XmlWorkflowItem.class, -1, -1); + return uniqueResult(context, criteriaQuery, false, XmlWorkflowItem.class); } } From 98015e469bab609f7f2b268a264f7550de9e601f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 23 Aug 2021 11:17:27 +0100 Subject: [PATCH 0163/1254] adding openAIREFunding valitation to IT --- .../org/dspace/app/rest/ExternalSourcesRestControllerIT.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 3497942be3..154be586c7 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 @@ -34,7 +34,9 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati ExternalSourceMatcher.matchExternalSource( "sherpaPublisher", "sherpaPublisher", false), ExternalSourceMatcher.matchExternalSource( - "pubmed", "pubmed", false) + "pubmed", "pubmed", false), + ExternalSourceMatcher.matchExternalSource( + "openAIREFunding", "openAIREFunding", false) ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(6))); } From 16e621928a63d3e91590a2e24b7a354f3e981e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 23 Aug 2021 12:05:45 +0100 Subject: [PATCH 0164/1254] adding openAIREFunding valitation to IT --- .../org/dspace/app/rest/ExternalSourcesRestControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 154be586c7..c6573dd744 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 @@ -38,7 +38,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati ExternalSourceMatcher.matchExternalSource( "openAIREFunding", "openAIREFunding", false) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(6))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(7))); } @Test From 032ffa6b41a6222c532c99107c5ef0327cdd33f3 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Mon, 23 Aug 2021 10:51:30 -0400 Subject: [PATCH 0165/1254] DS-3335 Simplify sword url construction --- .../java/org/dspace/sword/SWORDUrlManager.java | 18 +++++++----------- .../org/dspace/sword2/SwordUrlManager.java | 18 +++++++----------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java index ba466f5247..c145c9d367 100644 --- a/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java +++ b/dspace-sword/src/main/java/org/dspace/sword/SWORDUrlManager.java @@ -316,7 +316,7 @@ public class SWORDUrlManager { "Unable to construct service document urls, due to missing/invalid " + "config in sword.servicedocument.url and/or dspace.server.url"); } - depositUrl = validSWORDUrl("/servicedocument"); + depositUrl = buildSWORDUrl("servicedocument"); } return depositUrl; } @@ -347,7 +347,7 @@ public class SWORDUrlManager { "Unable to construct deposit urls, due to missing/invalid config in " + "sword.deposit.url and/or dspace.server.url"); } - depositUrl = validSWORDUrl("/deposit"); + depositUrl = buildSWORDUrl("deposit"); } return depositUrl; } @@ -439,7 +439,7 @@ public class SWORDUrlManager { "Unable to construct media-link urls, due to missing/invalid config in " + "media-link.url and/or dspace.server.url"); } - mlUrl = validSWORDUrl("/media-link"); + mlUrl = buildSWORDUrl("media-link"); } return mlUrl; } @@ -500,16 +500,12 @@ public class SWORDUrlManager { } /** - * Ensure configured paths when combined are vail URLs + * Return configured server path for SWORD url * * @param path the target SWORD endpoint - * @return a valid sword URL + * @return a sword URL */ - private String validSWORDUrl(String path) { - String[] splitDspaceUrl = dspaceUrl.split("//"); - String url = splitDspaceUrl[1] + "/" + swordPath + "/" + path; - url = url.replace("///", "/"); - url = url.replace("//", "/"); - return splitDspaceUrl[0] + "//" + url; + private String buildSWORDUrl(String path) { + return dspaceUrl + "/" + swordPath + "/" + path; } } diff --git a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java index 5b699fb575..f3b2cf4396 100644 --- a/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java +++ b/dspace-swordv2/src/main/java/org/dspace/sword2/SwordUrlManager.java @@ -107,7 +107,7 @@ public class SwordUrlManager { "Unable to construct service document urls, due to missing/invalid " + "config in sword2.url and/or dspace.server.url"); } - sUrl = validSWORDUrl("/swordv2"); + sUrl = buildSWORDUrl("swordv2"); } return sUrl; } @@ -305,7 +305,7 @@ public class SwordUrlManager { "Unable to construct service document urls, due to missing/invalid " + "config in swordv2-server.cfg servicedocument.url and/or dspace.server.url"); } - sdUrl = validSWORDUrl("/servicedocument"); + sdUrl = buildSWORDUrl("servicedocument"); } return sdUrl; } @@ -336,7 +336,7 @@ public class SwordUrlManager { "Unable to construct deposit urls, due to missing/invalid config in " + "swordv2-server.cfg deposit.url and/or dspace.server.url"); } - depositUrl = validSWORDUrl("/collection"); + depositUrl = buildSWORDUrl("collection"); } return depositUrl; } @@ -488,16 +488,12 @@ public class SwordUrlManager { } /** - * Ensure configured paths when combined are vail URLs + * Return configured server path for SWORD url * * @param path the target SWORD endpoint - * @return a valid sword URL + * @return a sword URL */ - private String validSWORDUrl(String path) { - String[] splitDspaceUrl = dspaceUrl.split("//"); - String url = splitDspaceUrl[1] + "/" + swordPath + "/" + path; - url = url.replace("///", "/"); - url = url.replace("//", "/"); - return splitDspaceUrl[0] + "//" + url; + private String buildSWORDUrl(String path) { + return dspaceUrl + "/" + swordPath + "/" + path; } } From 0816473c418bf28edaa76398578fad87d0348f24 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 24 Aug 2021 15:20:46 +0200 Subject: [PATCH 0166/1254] added check to avoide creating same versioning --- .../dspace/versioning/VersioningServiceImpl.java | 13 ++++++++++++- .../handler/ExternalSourceItemUriListHandler.java | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java index 7764e78844..a300f6a8b3 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java @@ -10,6 +10,7 @@ package org.dspace.versioning; import java.sql.SQLException; import java.util.Date; import java.util.List; +import java.util.Objects; import org.dspace.content.DCDate; import org.dspace.content.Item; @@ -196,7 +197,7 @@ public class VersioningServiceImpl implements VersioningService { int versionNumber) { try { Version version = versionDAO.create(context, new Version()); - if (versionNumber > 0) { + if (versionNumber > 0 && !isVersionExist(context, item, versionNumber)) { version.setVersionNumber(versionNumber); } else { version.setVersionNumber(getNextVersionNumer(context, history)); @@ -214,6 +215,16 @@ public class VersioningServiceImpl implements VersioningService { } } + private boolean isVersionExist(Context context, Item item, int versionNumber) throws SQLException { + VersionHistory history = versionHistoryService.findByItem(context, item); + if (Objects.isNull(history)) { + return false; + } + return history.getVersions().stream().filter(v -> v.getVersionNumber() == versionNumber) + .findFirst() + .isPresent(); + } + @Override public List getVersionsByHistory(Context c, VersionHistory vh) throws SQLException { List versions = versionDAO.findVersionsWithItems(c, vh, -1, -1); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java index 3adc8b9cfd..d619100bf6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/handler/ExternalSourceItemUriListHandler.java @@ -22,6 +22,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** + * This class extends the {@link ExternalSourceEntryItemUriListHandler} abstract class and implements it specifically + * for the List objects. * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ From 8bed933d903e6635c21faafd8e82326a670bd5aa Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 24 Aug 2021 09:40:09 -0400 Subject: [PATCH 0167/1254] Clearer prompts, reject empty password, test for empty password. #3363 --- .../org/dspace/eperson/EPersonCLITool.java | 10 ++-- .../org/dspace/eperson/EPersonCLIToolIT.java | 50 ++++++++++++++++++- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java index 5b6b28882e..8caa4d79b7 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java @@ -377,9 +377,13 @@ public class EPersonCLITool { modified = true; } if (command.hasOption(OPT_NEW_PASSWORD.getOpt())) { - char[] password1 = consoleService.readPassword("Enter new password for user %s", userName); - char[] password2 = consoleService.readPassword("Enter new password again to verify"); - if (Arrays.equals(password1, password2)) { + char[] password1 = consoleService.readPassword( + "Enter new password for user '%s': ", userName); + char[] password2 = consoleService.readPassword( + "Enter new password again to verify: "); + if (password1.length <= 0 || password2.length <= 0) { + System.err.println("The new password may not be empty."); + } else if (Arrays.equals(password1, password2)) { PasswordHash newHashedPassword = new PasswordHash(String.valueOf(password1)); Arrays.fill(password1, '\0'); // Obliterate cleartext passwords Arrays.fill(password2, '\0'); diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java index 9ecbd9feb4..ea9202ea31 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java @@ -7,13 +7,16 @@ */ package org.dspace.eperson; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import org.dspace.AbstractIntegrationTest; import org.dspace.util.FakeConsoleServiceImpl; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.ExpectedSystemExit; +import org.junit.contrib.java.lang.system.SystemErrRule; /** * @@ -27,6 +30,10 @@ public class EPersonCLIToolIT @Rule public final ExpectedSystemExit exit = ExpectedSystemExit.none(); + // Capture System.err() output. + @Rule + public final SystemErrRule sysErr = new SystemErrRule().enableLog(); + /** * Test --modify --newPassword * @throws Exception passed through. @@ -40,7 +47,7 @@ public class EPersonCLIToolIT // Create a source of "console" input. FakeConsoleServiceImpl consoleService = new FakeConsoleServiceImpl(); - consoleService.setPassword("secret".toCharArray()); + consoleService.setPassword(NEW_PASSWORD.toCharArray()); // Make certain that we know the eperson's email and old password hash. String email = eperson.getEmail(); @@ -54,11 +61,50 @@ public class EPersonCLIToolIT String[] argv = { "--modify", "--email", email, - "--newPassword", NEW_PASSWORD + "--newPassword" }; instance.main(argv); String newPasswordHash = eperson.getPassword(); assertNotEquals("Password hash did not change", oldPasswordHash, newPasswordHash); } + + /** + * Test --modify --newPassword with an empty password + * @throws Exception passed through. + */ + @Test + @SuppressWarnings("static-access") + public void testSetEmptyPassword() + throws Exception { + exit.expectSystemExitWithStatus(0); + System.out.println("main"); + + // Create a source of "console" input. + FakeConsoleServiceImpl consoleService = new FakeConsoleServiceImpl(); + consoleService.setPassword(new char[0]); + + // Make certain that we know the eperson's email and old password hash. + String email = eperson.getEmail(); + String oldPasswordHash = eperson.getPassword(); + + // Instantiate the unit under test. + EPersonCLITool instance = new EPersonCLITool(); + instance.setConsoleService(consoleService); + + // Test! + String[] argv = { + "--modify", + "--email", email, + "--newPassword" + }; + instance.main(argv); + + String newPasswordHash = eperson.getPassword(); + assertEquals("Password hash changed", oldPasswordHash, newPasswordHash); + + String response = sysErr.getLog(); + assertTrue("Standard error did not mention 'empty'", + response.contains("empty")); + } } From 3ce4b592fc679da0692530614ae5248eec2d2959 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 24 Aug 2021 11:45:06 -0400 Subject: [PATCH 0168/1254] Add test for mismatch between password and confirmation. #3363 --- .../org/dspace/eperson/EPersonCLITool.java | 8 +++- .../org/dspace/eperson/EPersonCLIToolIT.java | 47 ++++++++++++++++++- .../dspace/util/FakeConsoleServiceImpl.java | 40 ++++++++++++++-- 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java index 8caa4d79b7..7bab399b5e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java @@ -63,6 +63,9 @@ public class EPersonCLITool { private static final Option OPT_NEW_PASSWORD = new Option("w", "newPassword", false, "prompt for new password"); + static final String ERR_PASSWORD_EMPTY = "The new password may not be empty."; + static final String ERR_PASSWORD_NOMATCH = "Passwords do not match. Password not set"; + private static final EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); @@ -382,7 +385,7 @@ public class EPersonCLITool { char[] password2 = consoleService.readPassword( "Enter new password again to verify: "); if (password1.length <= 0 || password2.length <= 0) { - System.err.println("The new password may not be empty."); + System.err.println(ERR_PASSWORD_EMPTY); } else if (Arrays.equals(password1, password2)) { PasswordHash newHashedPassword = new PasswordHash(String.valueOf(password1)); Arrays.fill(password1, '\0'); // Obliterate cleartext passwords @@ -392,7 +395,7 @@ public class EPersonCLITool { eperson.setDigestAlgorithm(newHashedPassword.getAlgorithm()); modified = true; } else { - System.err.println("Passwords do not match. Password not set"); + System.err.println(ERR_PASSWORD_NOMATCH); } } if (command.hasOption(OPT_GIVENNAME.getOpt())) { @@ -442,6 +445,7 @@ public class EPersonCLITool { /** * Command to list known EPersons. */ + @SuppressWarnings("unused") private static int cmdList(Context context, String[] argv) { // XXX ideas: // specific user/netid diff --git a/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java b/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java index ea9202ea31..df86b0883a 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java +++ b/dspace-api/src/test/java/org/dspace/eperson/EPersonCLIToolIT.java @@ -25,6 +25,7 @@ import org.junit.contrib.java.lang.system.SystemErrRule; public class EPersonCLIToolIT extends AbstractIntegrationTest { private static final String NEW_PASSWORD = "secret"; + private static final String BAD_PASSWORD = "not secret"; // Handle System.exit() from unit under test. @Rule @@ -103,8 +104,50 @@ public class EPersonCLIToolIT String newPasswordHash = eperson.getPassword(); assertEquals("Password hash changed", oldPasswordHash, newPasswordHash); - String response = sysErr.getLog(); + String stderr = sysErr.getLog(); assertTrue("Standard error did not mention 'empty'", - response.contains("empty")); + stderr.contains(EPersonCLITool.ERR_PASSWORD_EMPTY)); + } + + /** + * Test --modify --newPassword with mismatched confirmation. + * This tests what happens when the user enters different strings at the + * first and second new-password prompts. + * @throws Exception passed through. + */ + @Test + @SuppressWarnings("static-access") + public void testSetMismatchedPassword() + throws Exception { + exit.expectSystemExitWithStatus(0); + System.out.println("main"); + + // Create a source of "console" input. + FakeConsoleServiceImpl consoleService = new FakeConsoleServiceImpl(); + consoleService.setPassword1(NEW_PASSWORD.toCharArray()); + consoleService.setPassword2(BAD_PASSWORD.toCharArray()); + + // Make certain that we know the eperson's email and old password hash. + String email = eperson.getEmail(); + String oldPasswordHash = eperson.getPassword(); + + // Instantiate the unit under test. + EPersonCLITool instance = new EPersonCLITool(); + instance.setConsoleService(consoleService); + + // Test! + String[] argv = { + "--modify", + "--email", email, + "--newPassword" + }; + instance.main(argv); + + String newPasswordHash = eperson.getPassword(); + assertEquals("Password hash changed", oldPasswordHash, newPasswordHash); + + String stderr = sysErr.getLog(); + assertTrue("Standard error did not indicate password mismatch", + stderr.contains(EPersonCLITool.ERR_PASSWORD_NOMATCH)); } } diff --git a/dspace-api/src/test/java/org/dspace/util/FakeConsoleServiceImpl.java b/dspace-api/src/test/java/org/dspace/util/FakeConsoleServiceImpl.java index e9220ccef7..f34d58410d 100644 --- a/dspace-api/src/test/java/org/dspace/util/FakeConsoleServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/util/FakeConsoleServiceImpl.java @@ -11,19 +11,30 @@ package org.dspace.util; * A test version of ConsoleService which supplies any password input that we * want. * + *

    This can return different passwords on even/odd calls, to test + * confirmation dialogs. See {@link setPassword1} and {@link setPassword2}. + * Use {@link setPassword} to set both identically. + * * @author Mark H. Wood */ public class FakeConsoleServiceImpl implements ConsoleService { private String prompt; private Object[] args; - private char[] password; + private char[] password1; + private char[] password2; + private int passwordCalls = 0; @Override public char[] readPassword(String prompt, Object... args) { this.prompt = prompt; this.args = args; - return this.password; + passwordCalls++; + if (passwordCalls % 2 != 0) { + return password1; + } else { + return password2; + } } public String getPasswordPrompt() { @@ -34,7 +45,30 @@ public class FakeConsoleServiceImpl return this.args; } + /** + * Set both passwords identically. + * @param password the password to be returned each time. + */ public void setPassword(char[] password) { - this.password = password; + setPassword1(password); + setPassword2(password); + } + + /** + * Set the password returned on odd calls to {@link readPassword}. + * @param password the password to be returned. + */ + public void setPassword1(char[] password) { + password1 = password; + } + + /** + * Set the password returned on even calls to {@link readPassword}, + * and reset the call counter. + * @param password the password to be returned. + */ + public void setPassword2(char[] password) { + password2 = password; + passwordCalls = 0; } } From d3c5f0473a96fc0b8c43df4cbf92aed0d0b56ab0 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 24 Aug 2021 11:59:54 -0400 Subject: [PATCH 0169/1254] Remove un-necessary IllegalArgumentException. #3363 --- dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java index 7bab399b5e..343ddcccfa 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPersonCLITool.java @@ -122,7 +122,6 @@ public class EPersonCLITool { new HelpFormatter().printHelp("user [options]", globalOptions); context.abort(); status = 1; - throw new IllegalArgumentException(); } if (context.isValid()) { From 7e02dbb882d14b3c93f5fc3b0c3b50a2c6720ade Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Tue, 24 Aug 2021 16:46:50 -0700 Subject: [PATCH 0170/1254] Adding IIIF cors configuration. --- .../java/org/dspace/app/rest/Application.java | 20 +++++++++-- .../org/dspace/app/rest/IIIFController.java | 3 +- .../security/WebSecurityConfiguration.java | 4 ++- .../app/rest/utils/ApplicationConfig.java | 33 +++++++++++++++---- dspace/config/modules/rest.cfg | 7 ++++ 5 files changed, 57 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 993c28af92..35e6fcbbf0 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 @@ -157,7 +157,12 @@ public class Application extends SpringBootServletInitializer { */ @Override public void addCorsMappings(@NonNull CorsRegistry registry) { - String[] corsAllowedOrigins = configuration.getCorsAllowedOrigins(); + // Get allowed origins for api and iiif endpoints. + String[] corsAllowedOrigins = configuration + .getCorsAllowedOrigins(configuration.getCorsAllowedOriginsConfig()); + String[] iiifAllowedOrigins = configuration + .getCorsAllowedOrigins(configuration.getIiifAllowedOriginsConfig()); + boolean corsAllowCredentials = configuration.getCorsAllowCredentials(); if (corsAllowedOrigins != null) { registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) @@ -166,7 +171,18 @@ public class Application extends SpringBootServletInitializer { .allowCredentials(corsAllowCredentials).allowedOrigins(corsAllowedOrigins) // 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-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER") + // 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 (iiifAllowedOrigins != null) { + registry.addMapping("/iiif/**").allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + .allowCredentials(corsAllowCredentials).allowedOrigins(iiifAllowedOrigins) + // 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") // Allow list of response headers allowed to be sent by us (the server) to the client .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java index 9ca6db00b9..f999e84bc0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java @@ -17,11 +17,12 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; + /** * Controller for IIIF Presentation and Search API. */ @RestController -@RequestMapping("/api/iiif") +@RequestMapping("/iiif") public class IIIFController { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index dcdafbc8c1..627862c508 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -80,7 +80,9 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { // Configure authentication requirements for ${dspace.server.url}/api/ URL only // NOTE: REST API is hardcoded to respond on /api/. Other modules (OAI, SWORD, etc) use other root paths. - http.antMatcher("/api/**") + http.requestMatchers() + .antMatchers("/api/**", "/iiif/**") + .and() // Enable Spring Security authorization on these paths .authorizeRequests() // Allow POST by anyone on the login endpoint 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 44f628d9e0..3dd8b4b927 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 @@ -32,6 +32,11 @@ public class ApplicationConfig { @Value("${rest.cors.allowed-origins}") private String[] corsAllowedOrigins; + // Allowed IIIF CORS origins ("Access-Control-Allow-Origin" header) + // Can be overridden in DSpace configuration + @Value("${rest.iiif.cors.allowed-origins}") + private String[] corsIiifAllowedOrigins; + // 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,25 +51,41 @@ public class ApplicationConfig { * Used by Application class * @return Array of URLs */ - public String[] getCorsAllowedOrigins() { + public String[] getCorsAllowedOrigins(String[] corsOrigins) { // Use "rest.cors.allowed-origins" if configured. Otherwise, default to the "dspace.ui.url" setting. - if (corsAllowedOrigins != null) { + if (corsOrigins != null) { // Ensure no allowed origins end in a trailing slash // Browsers send 'Origin' header without a trailing slash & Spring Security considers // http://example.org and http://example.org/ to be different Origins. - for (int i = 0; i < corsAllowedOrigins.length; i++) { - if (corsAllowedOrigins[i].endsWith("/")) { - corsAllowedOrigins[i] = StringUtils.removeEnd(corsAllowedOrigins[i], "/"); + for (int i = 0; i < corsOrigins.length; i++) { + if (corsOrigins[i].endsWith("/")) { + corsOrigins[i] = StringUtils.removeEnd(corsOrigins[i], "/"); } } - return corsAllowedOrigins; + return corsOrigins; } else if (uiURL != null) { return new String[] {uiURL}; } return null; } + /** + * Returns the rest.cors.allowed-origins defined in DSpace configuration. + * @return allowed origins + */ + public String[] getCorsAllowedOriginsConfig() { + return this.corsAllowedOrigins; + } + + /** + * Returns the rest.iiif.cors.allowed-origins defined in DSpace configuration. + * @return allowed origins + */ + public String[] getIiifAllowedOriginsConfig() { + return this.corsIiifAllowedOrigins; + } + /** * Return whether to allow credentials (cookies) on CORS requests. This is used to set the * CORS "Access-Control-Allow-Credentials" header in Application class. diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 2355c3c1b6..f71d4bf497 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -10,6 +10,13 @@ # (Requires reboot of servlet container, e.g. Tomcat, to reload) rest.cors.allowed-origins = ${dspace.ui.url} +# Only these origins (client URLs) can successfully communicate with the IIIF API. This +# allows XHR requests from remote IIIF clients. Defaults to ${dspace.ui.url} if unspecified +# (as the embedded IIIF client must have access to the API). Multiple allowed origin URLs may +# be comma separated. Wildcard value (*) is NOT SUPPORTED. # (Requires reboot of servlet +# container, e.g. Tomcat, to reload) +rest.iiif.cors.allowed-origins = ${dspace.ui.url} + # Whether or not to allow credentials (e.g. cookies) sent by the client/browser in CORS # requests (in "Access-Control-Allow-Credentials" header). # For DSpace, we default this to "true" to support external authentication via Shibboleth (and similar). From 127e427bbe1eb99bef0a5ca4b35d8b5c53c58526 Mon Sep 17 00:00:00 2001 From: tysonlt Date: Wed, 25 Aug 2021 11:45:20 +1000 Subject: [PATCH 0171/1254] Changes requested in PR 3322 --- .../dspace/app/itemimport/ItemImportCLITool.java | 2 +- .../org/dspace/app/util/RelationshipUtils.java | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java index 07c9eab8d9..31f74e89db 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java @@ -94,7 +94,7 @@ public class ItemImportCLITool { options.addOption("R", "resume", false, "resume a failed import (add only)"); options.addOption("q", "quiet", false, "don't display metadata"); - options.addOption("l", "relationships", false, "process relationships manifest"); + options.addOption("l", "relationships", false, "process relationships manifest (add only)"); options.addOption("h", "help", false, "help"); diff --git a/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java index 8a8ac00c7c..8df14c7d6d 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java @@ -17,11 +17,19 @@ public class RelationshipUtils { } /** - * Matches two Entity types to a Relationship Type from a set of Relationship Types. + * Matches two Entity types to a Relationship Type from a set of Relationship Types. * - * @param relTypes set of Relationship Types. - * @param targetType entity type of target. - * @param originType entity type of origin referer. + * Given a list of Relationship Types, this method will find a Relationship Type that + * is configured between the originType and the targetType, with the matching originTypeName. + * It will match a relationship between these two entities in either direction (eg leftward + * or rightward). + * + * Example: originType = Author, targetType = Publication, originTypeName = isAuthorOfPublication. + * + * @param relTypes set of Relationship Types in which to find a match. + * @param targetType entity type of target (eg. Publication). + * @param originType entity type of origin referer (eg. Author). + * @param originTypeName the name of the relationship (eg. isAuthorOfPublication) * @return null or matched Relationship Type. */ public static RelationshipType matchRelationshipType(List relTypes, String targetType, From 6ca754ec6c15d6754d28118cfeb512cec7d49f87 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 25 Aug 2021 07:48:15 -0400 Subject: [PATCH 0172/1254] [DS-3952] Check for required request attributes. --- .../IncompleteItemRequestException.java | 17 +++++++++ .../app/rest/model/RequestItemRest.java | 2 +- .../repository/RequestItemRepository.java | 38 ++++++++++++++++--- 3 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/IncompleteItemRequestException.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/IncompleteItemRequestException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/IncompleteItemRequestException.java new file mode 100644 index 0000000000..e28d5c3065 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/IncompleteItemRequestException.java @@ -0,0 +1,17 @@ +package org.dspace.app.rest.exception; + +/** + * Thrown to indicate that a mandatory Item Request attribute was not provided. + * + * @author Mark H. Wood + */ +public class IncompleteItemRequestException + extends UnprocessableEntityException { + public IncompleteItemRequestException(String message, Throwable cause) { + super(message, cause); + } + + public IncompleteItemRequestException(String message) { + super(message); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 94cacf6ec4..0a3fe203f0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -21,7 +21,7 @@ import org.dspace.app.rest.RestResourceController; @LinksRest(links = { @LinkRest(name = "bitstream", method = "getUuid"), @LinkRest(name = "item", method = "getUuid") -}) + }) public class RequestItemRest extends BaseObjectRest { public static final String NAME = "copyrequest"; 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 0eef3f0077..8f50fdae31 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 @@ -8,6 +8,8 @@ package org.dspace.app.rest.repository; +import static org.apache.commons.lang3.StringUtils.isBlank; + import java.io.IOException; import java.sql.SQLException; import java.util.UUID; @@ -17,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; +import org.dspace.app.rest.exception.IncompleteItemRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RequestItemRest; @@ -88,13 +91,36 @@ public class RequestItemRepository // Create the item request model object from the REST object. String token; try { - Bitstream bitstream = bitstreamService.find(ctx, - UUID.fromString(rir.getBitstreamId())); - Item item = itemService.find(ctx, - UUID.fromString(rir.getItemId())); + String bitstreamId = rir.getBitstreamId(); + if (isBlank(bitstreamId)) { + throw new IncompleteItemRequestException("A bitstream ID is required"); + } + Bitstream bitstream = bitstreamService.find(ctx, UUID.fromString(bitstreamId)); + if (null == bitstream) { + throw new IncompleteItemRequestException("That bitstream does not exist"); + } + + String itemId = rir.getItemId(); + if (isBlank(itemId)) { + throw new IncompleteItemRequestException("An item ID is required"); + } + Item item = itemService.find(ctx, UUID.fromString(itemId)); + if (null == item) { + throw new IncompleteItemRequestException("That item does not exist"); + } + + boolean allFiles = rir.isAllfiles(); + + String email = rir.getRequestEmail(); + if (isBlank(email)) { + throw new IncompleteItemRequestException("A submitter's email address is required"); + } + + String username = rir.getRequestName(); + String message = rir.getRequestMessage(); + token = requestItemService.createRequest(ctx, bitstream, item, - rir.isAllfiles(), rir.getRequestEmail(), rir.getRequestName(), - rir.getRequestMessage()); + allFiles, email, username, message); } catch (SQLException ex) { throw new RuntimeException("Item request not created.", ex); } From 5bc4d16175ee29bbbebc588cd674a36dbad8e140 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 25 Aug 2021 10:10:50 -0400 Subject: [PATCH 0173/1254] [DS-3952] Add requested CORS, CSRF tests. Fix license header. --- .../IncompleteItemRequestException.java | 7 ++ .../app/rest/RequestItemRepositoryIT.java | 74 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/IncompleteItemRequestException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/IncompleteItemRequestException.java index e28d5c3065..4d2980370d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/IncompleteItemRequestException.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/IncompleteItemRequestException.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.exception; /** 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 4f71df1a54..9f14d5935a 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 @@ -8,13 +8,17 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.text.IsEmptyString.emptyOrNullString; import static org.junit.Assert.assertEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; +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; @@ -23,6 +27,7 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; import java.util.Map; +import javax.servlet.http.Cookie; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.requestitem.RequestItem; @@ -291,4 +296,73 @@ public class RequestItemRepositoryIT Class instanceClass = instance.getDomainClass(); assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); } + + /** + * Verify that Spring Security's CSRF protection is working as we expect. + * We must test this using a simple non-GET request, as CSRF Tokens are not + * validated in a GET request. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testRefreshTokenWithInvalidCSRF() + throws Exception { + // Login via password to retrieve a valid token + String token = getAuthToken(eperson.getEmail(), password); + + // Remove "Bearer " from that token, so that we are left with the token itself + token = token.replace("Bearer ", ""); + + // Save token to an Authorization cookie + Cookie[] cookies = new Cookie[1]; + cookies[0] = new Cookie(AUTHORIZATION_COOKIE, token); + + getClient().perform(post(URI_ROOT) + .with(csrf().useInvalidToken().asHeader()) + .secure(true) + .cookie(cookies)) + // Should return a 403 Forbidden, for an invalid CSRF token + .andExpect(status().isForbidden()) + // Verify it includes our custom error reason (from DSpaceApiExceptionControllerAdvice) + .andExpect(status().reason(containsString("Invalid CSRF token"))) + // And, a new/updated token should be returned (as both server-side cookie and header) + // This is handled by DSpaceAccessDeniedHandler + .andExpect(cookie().exists("DSPACE-XSRF-COOKIE")) + .andExpect(header().exists("DSPACE-XSRF-TOKEN")); + + //Logout + getClient(token).perform(post("/api/authn/logout")) + .andExpect(status().isNoContent()); + } + + /** + * Verify that Spring Security's CORS settings are working as we expect. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testCannotReuseTokenFromUntrustedOrigin() + throws Exception { + // First, get a valid login token + String token = getAuthToken(eperson.getEmail(), password); + + // Verify token works + getClient(token).perform(get("/api/authn/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.okay", is(true))) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.type", is("status"))); + + // Test token cannot be used from an *untrusted* Origin + // (NOTE: this Origin is NOT listed in our 'rest.cors.allowed-origins' configuration) + getClient(token).perform(get(URI_ROOT) + .header("Origin", "https://example.org")) + // should result in a 403 error as Spring Security + //returns that for untrusted origins + .andExpect(status().isForbidden()); + + //Logout + getClient(token).perform(post("/api/authn/logout")) + .andExpect(status().isNoContent()); + } } From f25b51ae659583a93005674bf6f9d2575242c7ec Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 25 Aug 2021 10:42:17 -0700 Subject: [PATCH 0174/1254] Updated java docs. --- .../java/org/dspace/app/rest/utils/ApplicationConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 3dd8b4b927..96ec9c769e 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 @@ -49,10 +49,11 @@ public class ApplicationConfig { /** * Return the array of allowed origins (client URLs) for the CORS "Access-Control-Allow-Origin" header * Used by Application class + * @param corsOrigins list of allowed origins for the dspace api or iiif endpoints * @return Array of URLs */ public String[] getCorsAllowedOrigins(String[] corsOrigins) { - // Use "rest.cors.allowed-origins" if configured. Otherwise, default to the "dspace.ui.url" setting. + // Use origins from configuration. Otherwise, default to the "dspace.ui.url" setting. if (corsOrigins != null) { // Ensure no allowed origins end in a trailing slash // Browsers send 'Origin' header without a trailing slash & Spring Security considers @@ -79,7 +80,7 @@ public class ApplicationConfig { } /** - * Returns the rest.iiif.cors.allowed-origins defined in DSpace configuration. + * Returns the rest.iiif.cors.allowed-origins (for IIIF access) defined in DSpace configuration. * @return allowed origins */ public String[] getIiifAllowedOriginsConfig() { From eff7e11daa61f2f94f224920f91de19b019cc8c9 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 25 Aug 2021 15:52:26 -0700 Subject: [PATCH 0175/1254] Adding configurable log to manifest. --- .../iiif/model/generator/ImageContentGenerator.java | 4 +++- .../iiif/model/generator/ManifestGenerator.java | 10 ++++++++++ .../rest/iiif/service/AbstractResourceService.java | 2 ++ .../app/rest/iiif/service/ManifestService.java | 13 +++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java index d8ab207dd4..64ef8033d5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java @@ -66,7 +66,9 @@ public class ImageContentGenerator implements IIIFResource { } // Supporting a single service for each image resource. List services = new ArrayList<>(); - services.add(imageService.getService()); + if (imageService != null) { + services.add(imageService.getService()); + } imageContent.setServices(services); return imageContent; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 7451114284..509a336938 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -40,6 +40,7 @@ public class ManifestGenerator implements IIIFResource { private String identifier; private String label; + private Resource logo; private OtherContent seeAlso; // Becomes the "items" element in IIIF version 3.0 private CanvasItemsGenerator sequence; @@ -80,6 +81,10 @@ public class ManifestGenerator implements IIIFResource { this.label = label; } + public void addLogo(ImageContentGenerator logo) { + this.logo = logo.getResource(); + } + /** * Sets the behavior. A hint to the client as to the most appropriate method of displaying the resource * In IIIF Presentation API version 3.0 semantics this is the "behavior" @@ -179,6 +184,11 @@ public class ManifestGenerator implements IIIFResource { } else { manifest = new Manifest(identifier); } + if (logo != null) { + List logos = new ArrayList<>(); + logos.add((ImageContent) logo); + manifest.setLogos(logos); + } if (sequence != null) { manifest.addSequence((Sequence) sequence.getResource()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 1d94aaac39..6755142557 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -31,6 +31,7 @@ public abstract class AbstractResourceService { protected String IMAGE_SERVICE; protected String SEARCH_URL; protected String CLIENT_URL; + protected String IIIF_LOGO_IMAGE; protected String BITSTREAM_PATH_PREFIX; /** * Possible values: "paged" or "individuals". The property @@ -74,6 +75,7 @@ public abstract class AbstractResourceService { BITSTREAM_PATH_PREFIX = configurationService.getProperty("iiif.bitstream.url"); DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint"); CLIENT_URL = configurationService.getProperty("dspace.ui.url"); + IIIF_LOGO_IMAGE = configurationService.getProperty("iiif.logo.image"); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index ccd980633d..02e2b50f6a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -17,7 +17,9 @@ import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; +import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; import org.dspace.app.rest.iiif.model.generator.RangeGenerator; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.app.rest.iiif.model.info.Range; @@ -101,6 +103,7 @@ public class ManifestService extends AbstractResourceService { Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); manifestGenerator.setIdentifier(getManifestId(item.getID())); manifestGenerator.setLabel(item.getName()); + setLogoContainer(); addRelated(item); addSearchService(item); addMetadata(item.getMetadata()); @@ -336,4 +339,14 @@ public class ManifestService extends AbstractResourceService { } } } + + /** + * If the logo is defined in DSpace configuration, add to manifest + */ + private void setLogoContainer() { + if (IIIF_LOGO_IMAGE != null) { + imageContent.setIdentifier(IIIF_LOGO_IMAGE); + manifestGenerator.addLogo(imageContent); + } + } } From 0a8cdd5b59d0eb9b56bde920dd748ff2188c4b3f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 25 Aug 2021 16:07:24 -0700 Subject: [PATCH 0176/1254] Added logo url to iiif dspace configuration. --- dspace/config/dspace.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 5eccc58c3e..888b063e7a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1519,7 +1519,7 @@ log.report.dir = ${dspace.dir}/log #### IIIF CONFIGURATION #### # Base URL of the DSpace iiif API endpoint. -iiif.url = ${dspace.server.url}/api/iiif/ +iiif.url = ${dspace.server.url}/iiif/ # Base URL of the IIIF image server. iiif.image.server = http://localhost:8182/iiif/2/ @@ -1535,6 +1535,7 @@ iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams iiif.document.viewing.hint = individuals +# iiif.logo.image = https://image/url/i.png #---------------------------------------------------------------# #----------------REQUEST ITEM CONFIGURATION---------------------# From cbd37eb722d5799627c91045ac3d1d66964372cb Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 26 Aug 2021 15:36:37 -0700 Subject: [PATCH 0177/1254] Updated ITs to work with the new iiif endpoint. --- .../app/rest/iiif/IIIFRestRepositoryIT.java | 42 +++++++++---------- dspace/config/modules/iiif.cfg | 0 2 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 dspace/config/modules/iiif.cfg diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java index 895ca28a60..874bc6ed6b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java @@ -72,7 +72,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); context.restoreAuthSystemState(); // Status 500 - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().is(500)); } @@ -105,14 +105,14 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); // Default canvas size and label. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.service.profile", is("http://iiif.io/api/search/0/search"))) .andExpect(jsonPath("$.thumbnail.@id", Matchers.containsString("/iiif/2/" + bitstream1.getID()))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + 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(1200))) .andExpect(jsonPath("$.related.@id", @@ -153,11 +153,11 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); // Expect canvas label, width and height to match bitstream description. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) @@ -198,11 +198,11 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); // Expect canvas label, width and height to match bitstream description. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))) .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(3163))) .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(4220))) @@ -258,12 +258,12 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { // unless that has been changed in dspace configuration. This test assumes that DSpace // has been configured to return the "individuals" hint for documents to better support // search results in Mirador. That is the current dspace.cfg default setting. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.license", is("https://license.org"))) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) .andExpect(jsonPath("$.viewingHint", is("individuals"))) .andExpect(jsonPath("$.service").doesNotExist()); @@ -319,17 +319,17 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); // expect structures elements with label and canvas id. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) .andExpect(jsonPath("$.structures[1].label", is("Section 2"))) .andExpect(jsonPath("$.structures[1].canvases[0]", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c2"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c2"))) .andExpect(jsonPath("$.service").exists()); } @@ -360,12 +360,12 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.license", is("https://license.org"))) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) .andExpect(jsonPath("$.service").doesNotExist()); @@ -406,13 +406,13 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); // Expect seeAlso annotation list. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.license", is("https://license.org"))) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.seeAlso.@type", is("sc:AnnotationList"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) .andExpect(jsonPath("$.service").doesNotExist()); @@ -451,12 +451,12 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); // Image in the ORIGINAL bundle added as canvas; PDF ignored... - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.sequences[0].canvases", Matchers.hasSize(1))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/api/iiif/" + publicItem1.getID() + "/canvas/c0"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) .andExpect(jsonPath("$.service").doesNotExist()); } @@ -488,7 +488,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); // Single canvas. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/canvas/c0")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/canvas/c0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@type", is("sc:Canvas"))) .andExpect(jsonPath("$.images[0].@type", is("oa:Annotation"))); @@ -519,7 +519,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { } context.restoreAuthSystemState(); // Status 500. The item contains only one bitstream. The item manifest likewise contains one canvas. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/canvas/c2")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/canvas/c2")) .andExpect(status().is(500)); } @@ -558,7 +558,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); // Expect seeAlso AnnotationList if the dspace item includes an OtherContent bundle. - getClient().perform(get("/api/iiif/" + publicItem1.getID() + "/manifest/seeAlso")) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest/seeAlso")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@type", is("sc:AnnotationList"))) .andExpect(jsonPath("$.resources[0].@type", is("oa:Annotation"))) diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg new file mode 100644 index 0000000000..e69de29bb2 From 4491837d54ee056edbf3b55c16ccfb598689df44 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 26 Aug 2021 15:44:40 -0700 Subject: [PATCH 0178/1254] Moved the iiif cors configuration to separate iiif.cfg config file. --- .../java/org/dspace/app/rest/Application.java | 4 +++- .../rest/iiif/service/ManifestService.java | 2 -- .../app/rest/utils/ApplicationConfig.java | 20 ++++++++++++++++--- dspace/config/dspace.cfg | 1 + dspace/config/modules/iiif.cfg | 18 +++++++++++++++++ dspace/config/modules/rest.cfg | 7 ------- 6 files changed, 39 insertions(+), 13 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 35e6fcbbf0..4ee6570483 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 @@ -164,10 +164,12 @@ public class Application extends SpringBootServletInitializer { .getCorsAllowedOrigins(configuration.getIiifAllowedOriginsConfig()); boolean corsAllowCredentials = configuration.getCorsAllowCredentials(); + boolean iiifAllowCredentials = configuration.getIiifAllowCredentials(); if (corsAllowedOrigins != null) { registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid // for our Access-Control-Allow-Origin header + // for our Access-Control-Allow-Origin header .allowCredentials(corsAllowCredentials).allowedOrigins(corsAllowedOrigins) // 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", @@ -179,7 +181,7 @@ public class Application extends SpringBootServletInitializer { registry.addMapping("/iiif/**").allowedMethods(CorsConfiguration.ALL) // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid // for our Access-Control-Allow-Origin header - .allowCredentials(corsAllowCredentials).allowedOrigins(iiifAllowedOrigins) + .allowCredentials(iiifAllowCredentials).allowedOrigins(iiifAllowedOrigins) // 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") diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 02e2b50f6a..1a89d8144f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -17,9 +17,7 @@ import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; -import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; -import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; import org.dspace.app.rest.iiif.model.generator.RangeGenerator; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.app.rest.iiif.model.info.Range; 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 96ec9c769e..149153bad6 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 @@ -34,14 +34,19 @@ public class ApplicationConfig { // Allowed IIIF CORS origins ("Access-Control-Allow-Origin" header) // Can be overridden in DSpace configuration - @Value("${rest.iiif.cors.allowed-origins}") - private String[] corsIiifAllowedOrigins; + @Value("${iiif.cors.allowed-origins}") + private String[] iiifCorsAllowedOrigins; // 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}") private boolean corsAllowCredentials; + // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) + // Defaults to true. Can be overridden in DSpace configuration + @Value("${iiif.cors.allow-credentials:true}") + private boolean iiifCAllowCredentials; + // Configured User Interface URL (default: http://localhost:4000) @Value("${dspace.ui.url:http://localhost:4000}") private String uiURL; @@ -84,7 +89,7 @@ public class ApplicationConfig { * @return allowed origins */ public String[] getIiifAllowedOriginsConfig() { - return this.corsIiifAllowedOrigins; + return this.iiifCorsAllowedOrigins; } /** @@ -95,4 +100,13 @@ public class ApplicationConfig { public boolean getCorsAllowCredentials() { return corsAllowCredentials; } + + /** + * 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 getIiifAllowCredentials() { + return corsAllowCredentials; + } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 888b063e7a..0776d98f96 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1594,6 +1594,7 @@ include = ${module_dir}/irus-statistics.cfg include = ${module_dir}/oai.cfg include = ${module_dir}/rdf.cfg include = ${module_dir}/rest.cfg +include = ${module_dir}/iiif.cfg include = ${module_dir}/solr-statistics.cfg include = ${module_dir}/solrauthority.cfg include = ${module_dir}/spring.cfg diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index e69de29bb2..5f30b672e2 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -0,0 +1,18 @@ +# Only these origins (client URLs) can successfully communicate with the IIIF API. This +# allows XHR requests from remote IIIF clients. Defaults to ${dspace.ui.url} if unspecified +# (as the embedded IIIF client must have access to the API). Multiple allowed origin URLs may +# be comma separated. Wildcard value (*) is NOT SUPPORTED. # (Requires reboot of servlet +# container, e.g. Tomcat, to reload) +iiif.cors.allowed-origins = ${dspace.ui.url} + +# Whether or not to allow credentials (e.g. cookies) sent by the client/browser in CORS +# requests (in "Access-Control-Allow-Credentials" header). +# For the DSpace iiif endpoint, we default this to "false" . +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +iiif.cors.allow-credentials = false + + + + + + diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index f71d4bf497..2355c3c1b6 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -10,13 +10,6 @@ # (Requires reboot of servlet container, e.g. Tomcat, to reload) rest.cors.allowed-origins = ${dspace.ui.url} -# Only these origins (client URLs) can successfully communicate with the IIIF API. This -# allows XHR requests from remote IIIF clients. Defaults to ${dspace.ui.url} if unspecified -# (as the embedded IIIF client must have access to the API). Multiple allowed origin URLs may -# be comma separated. Wildcard value (*) is NOT SUPPORTED. # (Requires reboot of servlet -# container, e.g. Tomcat, to reload) -rest.iiif.cors.allowed-origins = ${dspace.ui.url} - # Whether or not to allow credentials (e.g. cookies) sent by the client/browser in CORS # requests (in "Access-Control-Allow-Credentials" header). # For DSpace, we default this to "true" to support external authentication via Shibboleth (and similar). From c72a289cc8e5afed14c1a0e23f0b2074164b55f8 Mon Sep 17 00:00:00 2001 From: Bill Branan Date: Mon, 30 Aug 2021 14:24:24 -0400 Subject: [PATCH 0179/1254] Updates log statement to include full error details when attempt to send registration email fails Also fixes a couple typos --- .../app/rest/repository/RegistrationRestRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 ba7583f1c5..a158fdfd8c 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 @@ -96,7 +96,7 @@ public class RegistrationRestRepository extends DSpaceRestRepository Date: Tue, 31 Aug 2021 12:41:47 +0200 Subject: [PATCH 0180/1254] added tests to prove bug of updating collection metadata will not produce a solr reindex --- .../app/rest/CollectionRestRepositoryIT.java | 137 ++++++++++++++++++ .../app/rest/CommunityRestRepositoryIT.java | 88 +++++++++++ 2 files changed, 225 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index e6f85c2fbe..09be02484f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -17,14 +17,21 @@ import static org.dspace.core.Constants.WRITE; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; +import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.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 java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -37,8 +44,11 @@ import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; @@ -3063,4 +3073,131 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform(get("/api/core/collections/search/findAdminAuthorized")) .andExpect(status().isUnauthorized()); } + + @Test + public void patchMetadataCheckReindexingTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("MyTest") + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/collections/search/findAdminAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains(CollectionMatcher + .matchProperties(col.getName(), col.getID(), col.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + List updateTitle = new ArrayList(); + Map value = new HashMap(); + value.put("value", "New Name"); + updateTitle.add(new ReplaceOperation("/metadata/dc.title/0", value)); + + String patchBody = getPatchContent(updateTitle); + getClient(adminToken).perform(patch("/api/core/collections/" + col.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata['dc.title'][0].value", is("New Name"))); + + getClient(adminToken).perform(get("/api/core/collections/search/findAdminAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void removeColAdminGroupToCheckReindexingTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("MyTest") + .withAdminGroup(eperson) + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/collections/search/findAdminAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains(CollectionMatcher + .matchProperties(col1.getName(), col1.getID(), col1.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/core/collections/" + col1.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + + getClient(epersonToken).perform(get("/api/core/collections/search/findAdminAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void addColAdminGroupToCheckReindexingTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("MyTest") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/collections/search/findAdminAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + AtomicReference idRef = new AtomicReference<>(); + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/collections/" + col1.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set( + UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/eperson/groups/" + idRef.get() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + eperson.getID() + )); + + getClient(epersonToken).perform(get("/api/core/collections/search/findAdminAuthorized") + .param("query", "MyTest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains(CollectionMatcher + .matchProperties(col1.getName(), col1.getID(), col1.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 7ce1acb85f..7fc56f0e5d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -16,6 +16,8 @@ import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataStringEnd import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; +import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -39,6 +41,7 @@ import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.projection.Projection; @@ -2592,4 +2595,89 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.page.totalElements", is(2))); } + @Test + public void removeComAdminGroupToCheckReindexingTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Root Community") + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("MyTestCom") + .withAdminGroup(eperson) + .build(); + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/communities/search/findAdminAuthorized") + .param("query", "MyTestCom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.contains(CommunityMatcher + .matchProperties(subCommunity.getName(), + subCommunity.getID(), + subCommunity.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(delete("/api/core/communities/" + subCommunity.getID() + "/adminGroup")) + .andExpect(status().isNoContent()); + + getClient(epersonToken).perform(get("/api/core/communities/search/findAdminAuthorized") + .param("query", "MyTestCom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void addComAdminGroupToCheckReindexingTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Root Community") + .build(); + + Community subCommunity = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("MyTestCom") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/communities/search/findAdminAuthorized") + .param("query", "MyTestCom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + AtomicReference idRef = new AtomicReference<>(); + ObjectMapper mapper = new ObjectMapper(); + GroupRest groupRest = new GroupRest(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/communities/" + subCommunity.getID() + "/adminGroup") + .content(mapper.writeValueAsBytes(groupRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set( + UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))) + ); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/eperson/groups/" + idRef.get() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + eperson.getID() + )); + + getClient(epersonToken).perform(get("/api/core/communities/search/findAdminAuthorized") + .param("query", "MyTestCom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.communities", Matchers.contains(CommunityMatcher + .matchProperties(subCommunity.getName(), + subCommunity.getID(), + subCommunity.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + } From c9e4bb8106369af7b430adace1224537e561471a Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Wed, 1 Sep 2021 13:51:48 +0200 Subject: [PATCH 0181/1254] Make no assumptions about configured scripts in ITs --- .../app/rest/ScriptRestRepositoryIT.java | 105 ++++++++++-------- 1 file changed, 56 insertions(+), 49 deletions(-) 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 d15265b8ab..9976fb4be1 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 @@ -22,6 +22,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.io.IOException; import java.sql.SQLException; import java.util.Arrays; +import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -81,24 +82,14 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(token).perform(get("/api/system/scripts")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.scripts", containsInAnyOrder( - ScriptMatcher.matchScript(scriptConfigurations.get(0).getName(), - scriptConfigurations.get(0).getDescription()), - ScriptMatcher.matchScript(scriptConfigurations.get(1).getName(), - scriptConfigurations.get(1).getDescription()), - ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(), - scriptConfigurations.get(2).getDescription()), - ScriptMatcher.matchScript(scriptConfigurations.get(3).getName(), - scriptConfigurations.get(3).getDescription()), - ScriptMatcher.matchScript(scriptConfigurations.get(4).getName(), - scriptConfigurations.get(4).getDescription()), - ScriptMatcher.matchScript(scriptConfigurations.get(5).getName(), - scriptConfigurations.get(5).getDescription()), - ScriptMatcher.matchScript(scriptConfigurations.get(6).getName(), - scriptConfigurations.get(6).getDescription()), - ScriptMatcher.matchScript(scriptConfigurations.get(7).getName(), - scriptConfigurations.get(7).getDescription()) + scriptConfigurations + .stream() + .map(scriptConfiguration -> ScriptMatcher.matchScript( + scriptConfiguration.getName(), + scriptConfiguration.getDescription() + )) + .collect(Collectors.toList()) ))); - } @@ -113,66 +104,74 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findAllScriptsPaginationTest() throws Exception { + List alphabeticScripts = + scriptConfigurations.stream() + .sorted(Comparator.comparing(s -> s.getClass().getName())) + .collect(Collectors.toList()); + + int totalPages = scriptConfigurations.size(); + int lastPage = totalPages - 1; String token = getAuthToken(admin.getEmail(), password); + // NOTE: the scripts are always returned in alphabetical order by fully qualified class name. getClient(token).perform(get("/api/system/scripts").param("size", "1")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.scripts", Matchers.not(Matchers.hasItem( - ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(), - scriptConfigurations.get(2).getDescription()) + ScriptMatcher.matchScript(alphabeticScripts.get(1).getName(), + alphabeticScripts.get(1).getDescription()) )))) .andExpect(jsonPath("$._embedded.scripts", hasItem( - ScriptMatcher.matchScript(scriptConfigurations.get(5).getName(), - scriptConfigurations.get(5).getDescription()) + ScriptMatcher.matchScript(alphabeticScripts.get(0).getName(), + alphabeticScripts.get(0).getDescription()) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=7"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("page=" + lastPage), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(8))) - .andExpect(jsonPath("$.page.totalElements", is(8))); + .andExpect(jsonPath("$.page.totalPages", is(totalPages))) + .andExpect(jsonPath("$.page.totalElements", is(totalPages))); getClient(token).perform(get("/api/system/scripts").param("size", "1").param("page", "1")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.scripts", hasItem( - ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(), - scriptConfigurations.get(2).getDescription()) + ScriptMatcher.matchScript(alphabeticScripts.get(1).getName(), + alphabeticScripts.get(1).getDescription()) ))) .andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem( - ScriptMatcher.matchScript(scriptConfigurations.get(5).getName(), - scriptConfigurations.get(5).getDescription()) + ScriptMatcher.matchScript(alphabeticScripts.get(0).getName(), + alphabeticScripts.get(0).getDescription()) )))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=7"), Matchers.containsString("size=1")))) + Matchers.containsString("/api/system/scripts?"), + Matchers.containsString("page=" + lastPage), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) .andExpect(jsonPath("$.page.number", is(1))) - .andExpect(jsonPath("$.page.totalPages", is(8))) - .andExpect(jsonPath("$.page.totalElements", is(8))); + .andExpect(jsonPath("$.page.totalPages", is(totalPages))) + .andExpect(jsonPath("$.page.totalElements", is(totalPages))); } @Test @@ -182,8 +181,16 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(token).perform(get("/api/system/scripts/mock-script")) .andExpect(status().isOk()) .andExpect(jsonPath("$", ScriptMatcher - .matchMockScript( - scriptConfigurations.get(scriptConfigurations.size() - 1).getOptions()))); + .matchMockScript( + scriptConfigurations + .stream() + .filter(scriptConfiguration + -> scriptConfiguration.getName().equals("mock-script")) + .findAny() + .orElseThrow() + .getOptions() + ) + )); } @Test From 653a2e740251c7cd97516308e6d0f4ece839ef2c Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Wed, 1 Sep 2021 15:08:03 +0200 Subject: [PATCH 0182/1254] Make no assumptions about metadata registry in ITs --- .../rest/MetadatafieldRestRepositoryIT.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) 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 32cb28f09c..56c8f637f1 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 @@ -21,7 +21,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Comparator; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.matcher.MetadataFieldMatcher; @@ -967,9 +970,13 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void findAllPaginationTest() throws Exception { - - // Determine number of metadata fields from database - int numberOfMdFields = ContentServiceFactory.getInstance().getMetadataFieldService().findAll(context).size(); + List alphabeticMdFields = + ContentServiceFactory.getInstance() + .getMetadataFieldService() + .findAll(context).stream() + .sorted(Comparator.comparing(mdf -> mdf.toString('.'))) + .collect(Collectors.toList()); + int numberOfMdFields = alphabeticMdFields.size(); // If we return 3 fields per page, determine number of pages we expect int pageSize = 3; @@ -983,9 +990,9 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration .andExpect(content().contentType(contentType)) // Metadata fields are returned alphabetically. So, look for the first 3 alphabetically .andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItems( - MetadataFieldMatcher.matchMetadataFieldByKeys("creativework","datePublished", null), - MetadataFieldMatcher.matchMetadataFieldByKeys("creativework", "editor", null), - MetadataFieldMatcher.matchMetadataFieldByKeys("creativework", "keywords", null) + MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(0)), + MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(1)), + MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(2)) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/metadatafields?"), @@ -1012,9 +1019,9 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration .andExpect(content().contentType(contentType)) // Metadata fields are returned alphabetically. So, look for the next 3 alphabetically .andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItems( - MetadataFieldMatcher.matchMetadataFieldByKeys("creativework","publisher", null), - MetadataFieldMatcher.matchMetadataFieldByKeys("creativeworkseries", "issn", null), - MetadataFieldMatcher.matchMetadataFieldByKeys("dc", "contributor", null) + MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(3)), + MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(4)), + MetadataFieldMatcher.matchMetadataField(alphabeticMdFields.get(5)) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/metadatafields?"), From 981e224feae096810ad0a95095c762d67e318d3f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 2 Sep 2021 09:55:28 +0200 Subject: [PATCH 0183/1254] added event that allow to update solr document of collection and community --- .../java/org/dspace/content/CollectionServiceImpl.java | 9 ++++++++- .../java/org/dspace/content/CommunityServiceImpl.java | 4 ++++ 2 files changed, 12 insertions(+), 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 380c0336af..f478783d29 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -524,6 +524,8 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i // register this as the admin group collection.setAdmins(admins); + context.addEvent(new Event(Event.MODIFY, Constants.COLLECTION, collection.getID(), + null, getIdentifiers(context, collection))); return admins; } @@ -540,6 +542,8 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i // Remove the link to the collection table. collection.setAdmins(null); + context.addEvent(new Event(Event.MODIFY, Constants.COLLECTION, collection.getID(), + null, getIdentifiers(context, collection))); } @Override @@ -657,8 +661,11 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i collection.clearModified(); } if (collection.isMetadataModified()) { - collection.clearDetails(); + context.addEvent(new Event(Event.MODIFY_METADATA, Constants.COLLECTION, collection.getID(), + collection.getDetails(),getIdentifiers(context, collection))); + collection.clearModified(); } + collection.clearDetails(); } @Override 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 73b1c062fd..221e230831 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -287,6 +287,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp // register this as the admin group community.setAdmins(admins); + context.addEvent(new Event(Event.MODIFY, Constants.COMMUNITY, community.getID(), + null, getIdentifiers(context, community))); return admins; } @@ -302,6 +304,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp // Remove the link to the community table. community.setAdmins(null); + context.addEvent(new Event(Event.MODIFY, Constants.COMMUNITY, community.getID(), + null, getIdentifiers(context, community))); } @Override From 76ea271eadb44f319d0416165ebb2465325686e3 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 2 Sep 2021 11:21:47 -0400 Subject: [PATCH 0184/1254] Address feedback. #2129 --- .../service/RequestItemService.java | 11 +- .../dspace/builder/RequestItemBuilder.java | 12 ++ .../util/AbstractBuilderCleanupUtil.java | 3 +- .../app/rest/model/RequestItemRest.java | 3 +- .../org/dspace/app/rest/model/RestModel.java | 1 - .../repository/RequestItemRepository.java | 5 +- .../app/rest/RequestItemRepositoryIT.java | 115 +++++++++++------- .../app/rest/matcher/RequestCopyMatcher.java | 2 - 8 files changed, 96 insertions(+), 56 deletions(-) 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 c7f5926463..9d274d1eb9 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 @@ -37,10 +37,17 @@ public interface RequestItemService { * @return the token of the request item * @throws SQLException if database error */ - public String createRequest(Context context, Bitstream bitstream, Item item, boolean allFiles, String reqEmail, - String reqName, String reqMessage) + public String createRequest(Context context, Bitstream bitstream, Item item, + boolean allFiles, String reqEmail, String reqName, String reqMessage) throws SQLException; + /** + * Retrieve a request by its token. + * + * @param context current DSpace session. + * @param token the token identifying the request. + * @return the matching request, or null if not found. + */ public RequestItem findByToken(Context context, String token); /** diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index bc57073e70..6c19448c1a 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -82,6 +82,18 @@ public class RequestItemBuilder requestItemService.delete(context, request); } + /** + * Delete a request identified by its token. If no such token is known, + * simply return. + * @param token the token identifying the request. + */ + static public void deleteRequestItem(String token) { + try (Context context = new Context()) { + RequestItem request = requestItemService.findByToken(context, token); + requestItemService.delete(context, request); + } + } + @Override protected RequestItemService getService() { return requestItemService; diff --git a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index 6cedf885ed..50850bd6ef 100644 --- a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -60,19 +60,18 @@ public class AbstractBuilderCleanupUtil { map.put(RelationshipBuilder.class.getName(), new LinkedList<>()); map.put(RelationshipTypeBuilder.class.getName(), new LinkedList<>()); map.put(EntityTypeBuilder.class.getName(), new LinkedList<>()); + map.put(RequestItemBuilder.class.getName(), new LinkedList<>()); map.put(PoolTaskBuilder.class.getName(), new LinkedList<>()); map.put(WorkflowItemBuilder.class.getName(), new LinkedList<>()); map.put(WorkspaceItemBuilder.class.getName(), new LinkedList<>()); map.put(BitstreamBuilder.class.getName(), new LinkedList<>()); map.put(BitstreamFormatBuilder.class.getName(), new LinkedList<>()); map.put(ClaimedTaskBuilder.class.getName(), new LinkedList<>()); - map.put(RequestItemBuilder.class.getName(), new LinkedList<>()); map.put(CollectionBuilder.class.getName(), new LinkedList<>()); map.put(CommunityBuilder.class.getName(), new LinkedList<>()); map.put(EPersonBuilder.class.getName(), new LinkedList<>()); map.put(GroupBuilder.class.getName(), new LinkedList<>()); map.put(BundleBuilder.class.getName(), new LinkedList<>()); - map.put(RequestItemBuilder.class.getName(), new LinkedList<>()); map.put(ItemBuilder.class.getName(), new LinkedList<>()); map.put(MetadataFieldBuilder.class.getName(), new LinkedList<>()); map.put(MetadataSchemaBuilder.class.getName(), new LinkedList<>()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 0a3fe203f0..64f1bb23d4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -24,7 +24,8 @@ import org.dspace.app.rest.RestResourceController; }) public class RequestItemRest extends BaseObjectRest { - public static final String NAME = "copyrequest"; + public static final String NAME = "itemrequest"; + public static final String PLURAL_NAME = NAME + "s"; public static final String CATEGORY = RestAddressableModel.TOOLS; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java index ecfe44e0ef..5bc85a58b2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java @@ -33,7 +33,6 @@ public interface RestModel extends Serializable { public static final String AUTHORIZATION = "authz"; public static final String VERSIONING = "versioning"; public static final String AUTHENTICATION = "authn"; - public static final String COPY_REQUEST = "copy_request"; public static final String TOOLS = "tools"; public String getType(); 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 8f50fdae31..793bf84d3e 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 @@ -69,8 +69,7 @@ public class RequestItemRepository @Override public Page findAll(Context context, Pageable pageable) { - // TODO ? There is no enumerator in RequestItemService - throw new UnsupportedOperationException("Not supported yet."); + throw new RepositoryMethodNotImplementedException(RequestItemRest.NAME, "findAll"); } @Override @@ -137,7 +136,7 @@ public class RequestItemRepository @Override public void delete(Context context, String token) throws AuthorizeException, RepositoryMethodNotImplementedException { - throw new RepositoryMethodNotImplementedException("RequestItemRest", "delete"); + throw new RepositoryMethodNotImplementedException(RequestItemRest.NAME, "delete"); } @Override 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 9f14d5935a..be95219add 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 @@ -7,6 +7,7 @@ */ 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.containsString; import static org.hamcrest.Matchers.is; @@ -26,7 +27,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; -import java.util.Map; import javax.servlet.http.Cookie; import com.fasterxml.jackson.databind.ObjectMapper; @@ -50,7 +50,6 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.web.servlet.MvcResult; /** * @@ -61,7 +60,7 @@ public class RequestItemRepositoryIT /** Where to find {@link RequestItem}s in the local URL namespace. */ public static final String URI_ROOT = REST_SERVER_URL + RequestItemRest.CATEGORY + '/' - + RequestItemRest.NAME + 's'; + + RequestItemRest.PLURAL_NAME; @Autowired(required = true) RequestItemConverter requestItemConverter; @@ -85,7 +84,7 @@ public class RequestItemRepositoryIT @Test public void testFindOneAuthenticated() throws Exception { - System.out.println("findOne"); + System.out.println("findOne (authenticated)"); context.turnOffAuthorisationSystem(); @@ -103,6 +102,8 @@ public class RequestItemRepositoryIT .createRequestItem(context, item, bitstream) .build(); + context.restoreAuthSystemState(); + // Test: can we find it? String authToken = getAuthToken(admin.getEmail(), password); final String uri = URI_ROOT + '/' + request.getToken(); @@ -111,10 +112,6 @@ public class RequestItemRepositoryIT .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( RequestCopyMatcher.matchRequestCopy(request)))); - - // Clean up. - bitstream.setDeleted(true); - context.restoreAuthSystemState(); } /** @@ -125,7 +122,7 @@ public class RequestItemRepositoryIT @Test public void testFindOneNotAuthenticated() throws Exception { - System.out.println("findOne"); + System.out.println("findOne (not authenticated)"); context.turnOffAuthorisationSystem(); @@ -143,6 +140,8 @@ public class RequestItemRepositoryIT .createRequestItem(context, item, bitstream) .build(); + context.restoreAuthSystemState(); + // Test: can we find it? final String uri = URI_ROOT + '/' + request.getToken(); getClient().perform(get(uri)) @@ -150,10 +149,6 @@ public class RequestItemRepositoryIT .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( RequestCopyMatcher.matchRequestCopy(request)))); - - // Clean up. - bitstream.setDeleted(true); - context.restoreAuthSystemState(); } /** @@ -168,9 +163,9 @@ public class RequestItemRepositoryIT throws SQLException, AuthorizeException, IOException, Exception { System.out.println("createAndReturn"); + // Create some necessary objects. context.turnOffAuthorisationSystem(); - // Create some necessary objects. Collection col = CollectionBuilder.createCollection(context, parentCommunity).build(); Item item = ItemBuilder.createItem(context, col).build(); @@ -180,6 +175,8 @@ public class RequestItemRepositoryIT .withMimeType("text/plain") .build(); + context.restoreAuthSystemState(); + // Fake up a request in REST form. RequestItemRest rir = new RequestItemRest(); rir.setBitstreamId(bitstream.getID().toString()); @@ -192,7 +189,7 @@ public class RequestItemRepositoryIT // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); String authToken = getAuthToken(admin.getEmail(), password); - MvcResult mvcResult = getClient(authToken) + getClient(authToken) .perform(post(URI_ROOT) .content(mapper.writeValueAsBytes(rir)) .contentType(contentType)) @@ -209,16 +206,11 @@ public class RequestItemRepositoryIT hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), // TODO should be an ISO datetime hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) ))) + .andDo((var result) -> saveToken(read(result.getResponse().getContentAsString(), "token"))) .andReturn(); // Clean up the created request. - String content = mvcResult.getResponse().getContentAsString(); - Map map = mapper.readValue(content, Map.class); - String requestToken = String.valueOf(map.get("token")); - RequestItem ri = requestItemService.findByToken(context, requestToken); - requestItemService.delete(context, ri); - - context.restoreAuthSystemState(); + RequestItemBuilder.deleteRequestItem(requestToken); } /** @@ -234,9 +226,9 @@ public class RequestItemRepositoryIT throws SQLException, AuthorizeException, IOException, Exception { System.out.println("createAndReturn"); + // Create some necessary objects. context.turnOffAuthorisationSystem(); - // Create some necessary objects. Collection col = CollectionBuilder.createCollection(context, parentCommunity).build(); Item item = ItemBuilder.createItem(context, col).build(); @@ -246,6 +238,8 @@ public class RequestItemRepositoryIT .withMimeType("text/plain") .build(); + context.restoreAuthSystemState(); + // Fake up a request in REST form. RequestItemRest rir = new RequestItemRest(); rir.setBitstreamId(bitstream.getID().toString()); @@ -257,8 +251,7 @@ public class RequestItemRepositoryIT // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); - MvcResult mvcResult = getClient() - .perform(post(URI_ROOT) + getClient().perform(post(URI_ROOT) .content(mapper.writeValueAsBytes(rir)) .contentType(contentType)) .andExpect(status().isCreated()) @@ -274,27 +267,11 @@ public class RequestItemRepositoryIT hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), // TODO should be an ISO datetime hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) ))) + .andDo((var result) -> saveToken(read(result.getResponse().getContentAsString(), "token"))) .andReturn(); // Clean up the created request. - String content = mvcResult.getResponse().getContentAsString(); - Map map = mapper.readValue(content, Map.class); - String requestToken = String.valueOf(map.get("token")); - RequestItem ri = requestItemService.findByToken(context, requestToken); - requestItemService.delete(context, ri); - - context.restoreAuthSystemState(); - } - - /** - * Test of getDomainClass method, of class RequestItemRepository. - */ - @Test - public void testGetDomainClass() { - System.out.println("getDomainClass"); - RequestItemRepository instance = new RequestItemRepository(); - Class instanceClass = instance.getDomainClass(); - assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); + RequestItemBuilder.deleteRequestItem(requestToken); } /** @@ -305,7 +282,7 @@ public class RequestItemRepositoryIT * @throws java.lang.Exception passed through. */ @Test - public void testRefreshTokenWithInvalidCSRF() + public void testCreateWithInvalidCSRF() throws Exception { // Login via password to retrieve a valid token String token = getAuthToken(eperson.getEmail(), password); @@ -317,7 +294,33 @@ public class RequestItemRepositoryIT Cookie[] cookies = new Cookie[1]; cookies[0] = new Cookie(AUTHORIZATION_COOKIE, token); + // Create some necessary objects. + context.turnOffAuthorisationSystem(); + + Collection col = CollectionBuilder.createCollection(context, + parentCommunity).build(); + Item item = ItemBuilder.createItem(context, col).build(); + InputStream is = new ByteArrayInputStream(new byte[0]); + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("/dev/null") + .withMimeType("text/plain") + .build(); + + context.restoreAuthSystemState(); + + // Fake up a request in REST form. + RequestItemRest rir = new RequestItemRest(); + rir.setBitstreamId(bitstream.getID().toString()); + rir.setItemId(item.getID().toString()); + rir.setRequestEmail(RequestItemBuilder.REQ_EMAIL); + rir.setRequestMessage(RequestItemBuilder.REQ_MESSAGE); + rir.setRequestName(RequestItemBuilder.REQ_NAME); + rir.setAllfiles(false); + + ObjectMapper mapper = new ObjectMapper(); getClient().perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType) .with(csrf().useInvalidToken().asHeader()) .secure(true) .cookie(cookies)) @@ -341,7 +344,7 @@ public class RequestItemRepositoryIT * @throws java.lang.Exception passed through. */ @Test - public void testCannotReuseTokenFromUntrustedOrigin() + public void testUntrustedOrigin() throws Exception { // First, get a valid login token String token = getAuthToken(eperson.getEmail(), password); @@ -365,4 +368,26 @@ public class RequestItemRepositoryIT getClient(token).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); } + + /** + * Test of getDomainClass method, of class RequestItemRepository. + */ + @Test + public void testGetDomainClass() { + System.out.println("getDomainClass"); + RequestItemRepository instance = new RequestItemRepository(); + Class instanceClass = instance.getDomainClass(); + assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); + } + + /** Saves the request token generated by creating an item request. */ + private String requestToken; + + /** + * Silly work-around because lambdas can't mutate external variables. + * @param token a request token to be remembered. + */ + private void saveToken(String token) { + requestToken = token; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java index b52a2cb011..70bf52ff2a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RequestCopyMatcher.java @@ -29,8 +29,6 @@ public class RequestCopyMatcher { public static Matcher matchRequestCopy(RequestItem request) { return allOf( - //hasJsonPath("$._links.bitstream", Matchers.not(Matchers.empty())), - //hasJsonPath("$._links.item", Matchers.not(Matchers.empty())), hasJsonPath("$.allfiles", is(request.isAllfiles())), hasJsonPath("$.requestEmail", is(request.getReqEmail())), hasJsonPath("$.requestName", is(request.getReqName())), From 28b4a5fcb36b1052535a1190b7e3d3be45f4f208 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 2 Sep 2021 14:21:47 -0700 Subject: [PATCH 0185/1254] Modified search service. --- .../rest/iiif/service/AnnotationService.java | 2 + .../app/rest/iiif/service/SearchService.java | 237 +----------------- .../iiif/service/WordHighlightSolrSearch.java | 2 + 3 files changed, 10 insertions(+), 231 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java new file mode 100644 index 0000000000..d9e23156be --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java @@ -0,0 +1,2 @@ +package org.dspace.app.rest.iiif.service;public interface AnnotionService { +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index b26e76e24c..611ac3ba54 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -7,38 +7,10 @@ */ package org.dspace.app.rest.iiif.service; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; import java.util.UUID; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import org.apache.commons.validator.routines.UrlValidator; import org.apache.logging.log4j.Logger; -import org.apache.solr.client.solrj.SolrQuery; -import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.client.solrj.impl.HttpSolrClient; -import org.apache.solr.client.solrj.impl.NoOpResponseParser; -import org.apache.solr.client.solrj.request.QueryRequest; -import org.apache.solr.common.params.CommonParams; -import org.apache.solr.common.util.NamedList; -import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; -import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; -import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; -import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; -import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; -import org.dspace.app.rest.iiif.service.util.IIIFUtils; -import org.dspace.discovery.SolrSearchCore; import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @@ -53,220 +25,23 @@ public class SearchService extends AbstractResourceService { private final boolean validationEnabled; - @Autowired - IIIFUtils utils; - - @Autowired - ContentAsTextGenerator contentAsText; - - @Autowired - CanvasGenerator canvas; - - @Autowired - AnnotationGenerator annotation; - - @Autowired - ManifestGenerator manifest; - - @Autowired - SearchResultGenerator searchResult; - - @Autowired - protected SolrSearchCore solrSearchCore; - public SearchService(ConfigurationService configurationService) { - setConfiguration(configurationService); validationEnabled = configurationService .getBooleanProperty("discovery.solr.url.validation.enabled", true); } /** - * Executes a search that is scoped to the manifest. + * Executes a search query for items in the current manifest. * * @param uuid dspace item uuid * @param query the solr query - * @return IIIF json + * @return IIIF search result with page coordinate annotations. */ public String searchWithinManifest(UUID uuid, String query) { - String json = getSolrSearchResponse(query, getManifestId(uuid)); - return getAnnotationList(json, uuid, query); - } - - /** - * Executes the Search API solr query. - * @param query encoded query terms - * @param manifestId the iiif manifest id - * - * @return json query response - */ - private String getSolrSearchResponse(String query, String manifestId) { - String json = ""; - String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("iiif.solr.search.url"); - UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); - if (urlValidator.isValid(solrService) || this.validationEnabled) { - HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); - solrServer.setUseMultiPartPost(true); - SolrQuery solrQuery = getSolrQuery(adjustQuery(query), manifestId); - QueryRequest req = new QueryRequest(solrQuery); - // return raw json response. - req.setResponseParser(new NoOpResponseParser("json")); - NamedList resp; - try { - resp = solrServer.request(req); - json = (String) resp.get("response"); - } catch (SolrServerException | IOException e) { - throw new RuntimeException("Unable to retrieve search response.", e); - } - } else { - log.error("Error while initializing solr, invalid url: " + solrService); - } - - return json; - } - - /** - * Wraps multi-word queries in parens. - * @param query the search query - * @return - */ - private String adjustQuery(String query) { - if (query.split(" ").length > 1) { - return '(' + query + ')'; - } - return query; - } - - /** - * Constructs a solr search URL. - * - * @param query the search terms - * @param manifestId the id of the manifest in which to search - * @return solr query - */ - private SolrQuery getSolrQuery(String query, String manifestId) { - SolrQuery solrQuery = new SolrQuery(); - solrQuery.set("q", "ocr_text:" + query + " AND manifest_url:\"" + manifestId + "\""); - solrQuery.set(CommonParams.WT, "json"); - solrQuery.set("hl", "true"); - solrQuery.set("hl.ocr.fl", "ocr_text"); - solrQuery.set("hl.ocr.contextBlock", "line"); - solrQuery.set("hl.ocr.contextSize", "2"); - solrQuery.set("hl.snippets", "10"); - solrQuery.set("hl.ocr.trackPages", "off"); - solrQuery.set("hl.ocr.limitBlock","page"); - solrQuery.set("hl.ocr.absoluteHighlights", "true"); - - return solrQuery; - } - - /** - * Generates a Search API response from the word_highlighting solr query response. - * - * The function assumes that the solr query responses contains page IDs - * (taken from the ALTO Page ID element) in the following format: - * Page.0, Page.1, Page.2.... - * - * The identifier values must be aligned with zero-based IIIF canvas identifiers: - * c0, c1, c2.... - * - * The convention convention for Alto IDs must be followed when indexing ALTO files - * into the word_highlighting solr index. If it is not, search responses will not - * match canvases. - * - * @param json solr search result - * @param uuid DSpace Item uuid - * @param query the solr query - * @return a search response in JSON - */ - private String getAnnotationList(String json, UUID uuid, String query) { - searchResult.setIdentifier(getManifestId(uuid) + "/search?q=" - + URLEncoder.encode(query, StandardCharsets.UTF_8)); - GsonBuilder builder = new GsonBuilder(); - Gson gson = builder.create(); - JsonObject body = gson.fromJson(json, JsonObject.class); - if (body == null) { - log.warn("Unable to process json response."); - return utils.asJson(searchResult.getResource()); - } - // outer ocr highlight element - JsonObject highs = body.getAsJsonObject("ocrHighlighting"); - // highlight entries - for (Map.Entry ocrIds: highs.entrySet()) { - // ocr_text - JsonObject ocrObj = ocrIds.getValue().getAsJsonObject().getAsJsonObject("ocr_text"); - // snippets array - if (ocrObj != null) { - for (JsonElement snippetArray : ocrObj.getAsJsonObject().get("snippets").getAsJsonArray()) { - String pageId = getCanvasId(snippetArray.getAsJsonObject().get("pages")); - for (JsonElement highlights : snippetArray.getAsJsonObject().getAsJsonArray("highlights")) { - for (JsonElement highlight : highlights.getAsJsonArray()) { - searchResult.addResource(getAnnotation(highlight, pageId, uuid)); - } - } - } - } - } - return utils.asJson(searchResult.getResource()); - } - - /** - * Returns the annotation generator for the highlight. - * @param highlight highlight element from solor response - * @param pageId page id from solr response - * @param uuid dspace item uuid - * @return generator for a single annotation - */ - private AnnotationGenerator getAnnotation(JsonElement highlight, String pageId, UUID uuid) { - JsonObject hcoords = highlight.getAsJsonObject(); - String text = (hcoords.get("text").getAsString()); - int ulx = hcoords.get("ulx").getAsInt(); - int uly = hcoords.get("uly").getAsInt(); - int lrx = hcoords.get("lrx").getAsInt(); - int lry = hcoords.get("lry").getAsInt(); - String w = Integer.toString(lrx - ulx); - String h = Integer.toString(lry - uly); - String params = ulx + "," + uly + "," + w + "," + h; - return createSearchResultAnnotation(params, text, pageId, uuid); - } - - /** - * Returns position of canvas by extracting from the pages id element. - * @param element the pages element - * @return canvas id - */ - private String getCanvasId(JsonElement element) { - JsonArray pages = element.getAsJsonArray(); - JsonObject page = pages.get(0).getAsJsonObject(); - String[] identArr = page.get("id").getAsString().split("\\."); - // the canvas id. - return "c" + identArr[1]; - } - - /** - * Creates annotation with word highlight coordinates. - * - * @param params word coordinate parameters used for highlighting. - * @param text word text - * @param pageId the page id returned by solr - * @param uuid the dspace item identifier - * @return a single annotation object that contains word highlights on a single page (canvas) - */ - private AnnotationGenerator createSearchResultAnnotation(String params, String text, String pageId, UUID uuid) { - annotation.setIdentifier(IIIF_ENDPOINT + uuid + "/annot/" + pageId + "-" - + params); - canvas.setIdentifier(IIIF_ENDPOINT + uuid + "/canvas/" + pageId + "#xywh=" - + params); - annotation.setOnCanvas(canvas); - contentAsText.setText(text); - annotation.setResource(contentAsText); - annotation.setMotivation(AnnotationGenerator.PAINTING); - List withinList = new ArrayList<>(); - manifest.setIdentifier(getManifestId(uuid)); - manifest.setLabel("Search within manifest."); - withinList.add(manifest); - annotation.setWithin(withinList); - return annotation; + // Support for https://github.com/dbmdz/solr-ocrhighlighting + AnnotationService annotationService = + new WordHighlightSolrSearch(uuid, getManifestId(uuid), IIIF_ENDPOINT, validationEnabled); + return annotationService.getSolrSearchResponse(query); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java new file mode 100644 index 0000000000..3898b1a84e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java @@ -0,0 +1,2 @@ +package org.dspace.app.rest.iiif.service;public class WordHighlightAltoSearch { +} From 3512318ce176a1b73769f71dbb424856d5338165 Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Fri, 3 Sep 2021 17:06:31 +1000 Subject: [PATCH 0186/1254] fix checkstyle violations --- .../src/main/java/org/dspace/app/util/RelationshipUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java index 8df14c7d6d..c63d2fdfdf 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RelationshipUtils.java @@ -19,12 +19,12 @@ public class RelationshipUtils { /** * Matches two Entity types to a Relationship Type from a set of Relationship Types. * - * Given a list of Relationship Types, this method will find a Relationship Type that + * Given a list of Relationship Types, this method will find a Relationship Type that * is configured between the originType and the targetType, with the matching originTypeName. * It will match a relationship between these two entities in either direction (eg leftward * or rightward). * - * Example: originType = Author, targetType = Publication, originTypeName = isAuthorOfPublication. + * Example: originType = Author, targetType = Publication, originTypeName = isAuthorOfPublication. * * @param relTypes set of Relationship Types in which to find a match. * @param targetType entity type of target (eg. Publication). From 0fa5ef914f6a0922b31ce12cd622db01c6b54beb Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Fri, 3 Sep 2021 17:07:27 +1000 Subject: [PATCH 0187/1254] remove relationships option (always enabled) --- .../java/org/dspace/app/itemimport/ItemImportCLITool.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java index 31f74e89db..7cad97df31 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java @@ -94,7 +94,6 @@ public class ItemImportCLITool { options.addOption("R", "resume", false, "resume a failed import (add only)"); options.addOption("q", "quiet", false, "don't display metadata"); - options.addOption("l", "relationships", false, "process relationships manifest (add only)"); options.addOption("h", "help", false, "help"); @@ -108,7 +107,6 @@ public class ItemImportCLITool { String[] collections = null; // db ID or handles boolean isTest = false; boolean isResume = false; - boolean processRelationships = false; boolean useWorkflow = false; boolean useWorkflowSendEmail = false; boolean isQuiet = false; @@ -186,10 +184,6 @@ public class ItemImportCLITool { collections = line.getOptionValues('c'); } - if (line.hasOption('l')) { //relationships - processRelationships = true; - } - if (line.hasOption('R')) { isResume = true; System.out @@ -309,7 +303,6 @@ public class ItemImportCLITool { myloader.setResume(isResume); myloader.setUseWorkflow(useWorkflow); myloader.setUseWorkflowSendEmail(useWorkflowSendEmail); - myloader.setProcessRelationships(processRelationships); myloader.setQuiet(isQuiet); // create a context From db3d4ed374aa98977f961a6c9522bb882975f155 Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Fri, 3 Sep 2021 17:10:53 +1000 Subject: [PATCH 0188/1254] always process relationships, less noisy output --- .../app/itemimport/ItemImportServiceImpl.java | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) 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 f2916bc8a7..9214fc0570 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 @@ -276,12 +276,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea if (skipItems.containsKey(dircontents[i])) { System.out.println("Skipping import of " + dircontents[i]); - if (processRelationships) { - //we still need the item in the map for relationship linking - String skippedHandle = skipItems.get(dircontents[i]); - Item skippedItem = (Item) handleService.resolveToObject(c, skippedHandle); - itemFolderMap.put(dircontents[i], skippedItem); - } + //we still need the item in the map for relationship linking + String skippedHandle = skipItems.get(dircontents[i]); + Item skippedItem = (Item) handleService.resolveToObject(c, skippedHandle); + itemFolderMap.put(dircontents[i], skippedItem); } else { List clist; @@ -305,19 +303,15 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea Item item = addItem(c, clist, sourceDir, dircontents[i], mapOut, template); - if (processRelationships) { - itemFolderMap.put(dircontents[i], item); - } + itemFolderMap.put(dircontents[i], item); c.uncacheEntity(item); System.out.println(i + " " + dircontents[i]); } } - if (processRelationships) { - //now that all items are imported, iterate again to link relationships - addRelationships(c, sourceDir); - } + //now that all items are imported, iterate again to link relationships + addRelationships(c, sourceDir); } finally { if (mapOut != null) { @@ -336,16 +330,12 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea */ protected void addRelationships(Context c, String sourceDir) throws Exception { - System.out.println("Linking relationships"); - for (Map.Entry itemEntry : itemFolderMap.entrySet()) { String folderName = itemEntry.getKey(); String path = sourceDir + File.separatorChar + folderName; Item item = itemEntry.getValue(); - System.out.println("Adding relationships from directory " + folderName); - //look for a 'relationship' manifest Map> relationships = processRelationshipFile(path, "relationships"); if (!relationships.isEmpty()) { @@ -500,8 +490,6 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } } - } else { - System.out.println("\tNo relationships file found."); } return result; From ad0c19230d541773dc5e2cbc117850e590d6585b Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Fri, 3 Sep 2021 17:11:47 +1000 Subject: [PATCH 0189/1254] Abstract relationship lookup into RelationshipUtils --- .../dspace/app/bulkedit/MetadataImport.java | 32 ++----------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 0db0cc45be..3443f7199d 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 @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.RelationshipUtils; import org.dspace.authority.AuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; import org.dspace.authority.service.AuthorityValueService; @@ -1793,36 +1794,7 @@ public class MetadataImport extends DSpaceRunnable relTypes, String targetType, String originType, String originTypeName) { - RelationshipType foundRelationshipType = null; - if (originTypeName.split("\\.").length > 1) { - originTypeName = originTypeName.split("\\.")[1]; - } - for (RelationshipType relationshipType : relTypes) { - // Is origin type leftward or righward - boolean isLeft = false; - if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType)) { - isLeft = true; - } - if (isLeft) { - // Validate typeName reference - if (!relationshipType.getLeftwardType().equalsIgnoreCase(originTypeName)) { - continue; - } - if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) && - relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) { - foundRelationshipType = relationshipType; - } - } else { - if (!relationshipType.getRightwardType().equalsIgnoreCase(originTypeName)) { - continue; - } - if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) && - relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) { - foundRelationshipType = relationshipType; - } - } - } - return foundRelationshipType; + return RelationshipUtils.matchRelationshipType(relTypes, targetType, originType, originTypeName); } } From 8e955a75a6319f9d008e74f775339b72e5cd498c Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Fri, 3 Sep 2021 15:40:06 +0200 Subject: [PATCH 0190/1254] 83338: Fix from/to indices --- .../patch/operation/DSpaceObjectMetadataMoveOperation.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataMoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataMoveOperation.java index 1cda63d632..6684bd5f9d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataMoveOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataMoveOperation.java @@ -44,10 +44,10 @@ public class DSpaceObjectMetadataMoveOperation extends P public R perform(Context context, R resource, Operation operation) throws SQLException { DSpaceObjectService dsoService = ContentServiceFactory.getInstance().getDSpaceObjectService(resource); MetadataField metadataField = metadataPatchUtils.getMetadataField(context, operation); - String indexInPath = metadataPatchUtils.getIndexFromPath(operation.getPath()); - String indexToMoveFrom = metadataPatchUtils.getIndexFromPath(((MoveOperation) operation).getFrom()); + String indexTo = metadataPatchUtils.getIndexFromPath(operation.getPath()); + String indexFrom = metadataPatchUtils.getIndexFromPath(((MoveOperation) operation).getFrom()); - move(context, resource, dsoService, metadataField, indexInPath, indexToMoveFrom); + move(context, resource, dsoService, metadataField, indexFrom, indexTo); return resource; } From 4511b5e9e869fa380de58c10d023efc0b0231167 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Fri, 3 Sep 2021 09:06:33 -0500 Subject: [PATCH 0191/1254] use correct license when one is not supplied --- .../src/main/java/org/dspace/content/packager/PackageUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java index e45aa7d699..c127b48af9 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java @@ -183,7 +183,7 @@ public class PackageUtils { Item item, Collection collection) throws SQLException, IOException, AuthorizeException { if (license == null) { - license = collection.getLicenseCollection(); + license = collectionService.getLicense(collection); } InputStream lis = new ByteArrayInputStream(license.getBytes()); From 3edc34c451843818c371b9e9f18c7dc7b49f400c Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 3 Sep 2021 12:22:00 -0700 Subject: [PATCH 0192/1254] Mostly code improvements with one functional change in how the iiif bundle is chosen. --- .../model/generator/AnnotationGenerator.java | 66 ++--- .../iiif/model/generator/CanvasGenerator.java | 73 ++--- .../model/generator/CanvasItemsGenerator.java | 19 +- .../generator/ContentAsTextGenerator.java | 1 + .../generator/ContentSearchGenerator.java | 5 +- .../generator/ExternalLinksGenerator.java | 46 +-- .../generator/ImageServiceGenerator.java | 2 +- .../model/generator/ManifestGenerator.java | 45 ++- .../model/generator/ProfileGenerator.java | 3 + .../generator/PropertyValueGenerator.java | 20 +- .../iiif/model/generator/RangeGenerator.java | 2 +- .../iiif/service/AbstractResourceService.java | 16 +- .../iiif/service/AnnotationListService.java | 58 ++-- .../rest/iiif/service/AnnotationService.java | 18 +- .../iiif/service/CanvasLookupService.java | 7 +- .../app/rest/iiif/service/CanvasService.java | 22 +- .../rest/iiif/service/ManifestService.java | 133 ++------- .../app/rest/iiif/service/SearchService.java | 11 +- .../rest/iiif/service/SequenceService.java | 125 +++++++++ .../iiif/service/WordHighlightSolrSearch.java | 264 +++++++++++++++++- .../app/rest/iiif/service/util/IIIFUtils.java | 11 +- 21 files changed, 597 insertions(+), 350 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java index 8e5887d335..508dc6e58c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java @@ -12,80 +12,52 @@ import java.util.List; import de.digitalcollections.iiif.model.Motivation; import de.digitalcollections.iiif.model.openannotation.Annotation; -import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; /** - * Facade for the IIIF Presentation API version 2.2.1 domain model. Annotations associate - * content resources and commentary with a canvas. - * - * This facade is provided for serializing AnnotationList and Search query responses. + * Annotations associate content resources and commentary with a canvas. + * This is used for the otherContent AnnotationList and Search response. */ @Component -@RequestScope +@Scope("prototype") public class AnnotationGenerator implements IIIFResource { - private String identifier; - private Canvas onCanvas; - private Motivation motivation; - private ContentAsTextGenerator contentAsText; - private ExternalLinksGenerator otherContent; - // Renamed to "partOf" in IIIF v 3.0 - private List within; - + public static final String TYPE = "sc:AnnotationList"; public static final Motivation PAINTING = new Motivation("sc:painting"); public static final Motivation COMMENTING = new Motivation("oa:commenting"); public static final Motivation LINKING = new Motivation("oa:linking"); - public void setIdentifier(String identifier) { - this.identifier = identifier; + private Annotation annotation; + + public AnnotationGenerator(String identifier, Motivation motivation) { + annotation = new Annotation(identifier, motivation); } - public void setMotivation(Motivation motivation) { - this.motivation = motivation; - } - - public void setOnCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { - this.onCanvas = (Canvas) canvas.getResource(); + public void setOnCanvas(CanvasGenerator canvas) { + annotation.setOn(canvas.getResource()); } public void setResource(org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator contentAsText) { - this.contentAsText = contentAsText; + annotation.setResource(contentAsText.getResource()); } public void setResource(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { - this.otherContent = otherContent; + annotation.setResource(otherContent.getResource()); } public void setWithin(List within) { - this.within = within; + List manifests = new ArrayList<>(); + for (ManifestGenerator manifest : within) { + manifests.add(manifest.getResource()); + } + // property renamed to partOf in v3 + annotation.setWithin(manifests); } @Override public Resource getResource() { - Annotation annotation = new Annotation(identifier, motivation); - if (contentAsText != null) { - annotation.setResource(contentAsText.getResource()); - } - if (otherContent != null) { - annotation.setResource(otherContent.getResource()); - } - if (onCanvas != null) { - annotation.setOn(onCanvas); - } - if (within != null) { - List manifests = new ArrayList<>(); - for (ManifestGenerator manifest : within) { - manifests.add(manifest.getResource()); - } - annotation.setWithin(manifests); - } - within = null; - identifier = null; - otherContent = null; - onCanvas = null; return annotation; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index f4f611f21d..19a9a3d894 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -7,17 +7,15 @@ */ package org.dspace.app.rest.iiif.model.generator; -import java.util.ArrayList; -import java.util.List; - import de.digitalcollections.iiif.model.ImageContent; +import de.digitalcollections.iiif.model.PropertyValue; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** - * Facade for Presentation API version 2.1.1 Canvas model. + * Presentation API version 2.1.1 Canvas model. * * Changes a Presentation API version 3.0 will likely require new fields for * this class to support multiple media types. @@ -26,19 +24,10 @@ import org.springframework.stereotype.Component; @Scope("prototype") public class CanvasGenerator implements IIIFResource { - String identifier; - String label; - Integer height; - Integer width; - List> imageContent = new ArrayList<>(); - Resource thumbContent; + Canvas canvas; - /** - * Canvases must be identified by a URI and it must be an HTTP(s) URI. - * @param identifier - */ - public void setIdentifier(String identifier) { - this.identifier = identifier; + public CanvasGenerator(String identifier) { + this.canvas = new Canvas(identifier); } /** @@ -46,7 +35,7 @@ public class CanvasGenerator implements IIIFResource { * @param label */ public void setLabel(String label) { - this.label = label; + canvas.setLabel(new PropertyValue(label)); } /** @@ -54,7 +43,7 @@ public class CanvasGenerator implements IIIFResource { * @param height */ public void setHeight(int height) { - this.height = height; + canvas.setHeight(height); } /** @@ -62,57 +51,35 @@ public class CanvasGenerator implements IIIFResource { * @param width */ public void setWidth(int width) { - this.width = width; + canvas.setWidth(width); } /** - * The ImageContent resource to be assigned to the canvas. + * Add to ImageContent resources that will be assigned to the canvas. * @param imageContent */ public void addImage(Resource imageContent) { - this.imageContent.add(imageContent); + canvas.addImage((ImageContent) imageContent); } /** - * The ImageContent resource to be assigned as the thumbnail the canvas. + * The Thumbnail resource that will be assigned to the canvas. * @param thumbnail */ public void addThumbnail(ImageContentGenerator thumbnail) { - this.thumbContent = thumbnail.getResource(); + canvas.addThumbnail((ImageContent) thumbnail.getResource()); } + /** + * Returns the canvas. + * @return canvas model + */ @Override public Resource getResource() { - /** - * The Canvas resource typically includes image content. - */ - Canvas canvas; - if (identifier == null) { - throw new RuntimeException("The Canvas resource requires an identifier."); - } - if (label != null) { - canvas = new Canvas(identifier, label); - } else { - canvas = new Canvas(identifier); - } - if (imageContent.size() > 0) { - if (height == null || width == null) { - throw new RuntimeException("The Canvas resource requires both height and width dimensions."); - } - canvas.setWidth(width); - canvas.setHeight(height); - for (Resource res : imageContent) { - canvas.addImage((ImageContent) res); - } - if (thumbContent != null) { - canvas.addThumbnail((ImageContent) thumbContent); - } - } - // Reset properties after each use. - identifier = null; - imageContent.clear(); - label = null; - +// if (canvas.getHeight() == null || canvas.getWidth() == null) { +// throw new RuntimeException("The Canvas resource requires both height and width dimensions."); +// } return canvas; } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java index a31d487f63..fb457314c7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java @@ -7,16 +7,16 @@ */ package org.dspace.app.rest.iiif.model.generator; -import java.util.ArrayList; -import java.util.List; + import java.util.ArrayList; + import java.util.List; -import de.digitalcollections.iiif.model.OtherContent; -import de.digitalcollections.iiif.model.sharedcanvas.Canvas; -import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import de.digitalcollections.iiif.model.sharedcanvas.Sequence; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; + import de.digitalcollections.iiif.model.OtherContent; + import de.digitalcollections.iiif.model.sharedcanvas.Canvas; + import de.digitalcollections.iiif.model.sharedcanvas.Resource; + import de.digitalcollections.iiif.model.sharedcanvas.Sequence; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Component; + import org.springframework.web.context.annotation.RequestScope; /** * Facade for the current Presentation API version 2.1.1 domain model's Sequence class. @@ -62,7 +62,6 @@ public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.gene * @param canvas wrapper for Canvas */ public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { - this.canvas.add((Canvas) canvas.getResource()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java index 0c5c9039d0..1be4806e43 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java @@ -19,6 +19,7 @@ public class ContentAsTextGenerator implements IIIFResource { public void setText(String text) { this.text = text; } + @Override public Resource getResource() { if (text == null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java index 9876ff0bd4..1e9d7ba16c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java @@ -21,8 +21,9 @@ import org.springframework.web.context.annotation.RequestScope; /** * Facade for the Search API version 1.0 search service description. * - * Added to the Manifest of items that support full-text searching, identified - * by the "relationship.type: IIIFSearchable" DSpace metadata field. + * Added to the Manifest when the item supports full-text searching, identified + * by the "dspace.entity.type: IIIFSearchable" DSpace metadata field. NOTE: the + * entity.type is going to be abandoned in favor of another DSO metadata field. */ @Component @RequestScope diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java index 0ad003b12b..410a4553e2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java @@ -8,9 +8,8 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.OtherContent; -import de.digitalcollections.iiif.model.PropertyValue; import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** @@ -23,22 +22,13 @@ import org.springframework.stereotype.Component; * and items instead. */ @Component +@Scope("prototype") public class ExternalLinksGenerator implements IIIFResource { - @Autowired - PropertyValueGenerator propertyValue; + private OtherContent otherContent; - private String identifier; - private String format; - private PropertyValue label; - private String type; - - /** - * Sets the mandatory identifier. - * @param identifier - */ - public void setIdentifier(String identifier) { - this.identifier = identifier; + public ExternalLinksGenerator(String identifier) { + otherContent = new OtherContent(identifier); } /** @@ -46,7 +36,7 @@ public class ExternalLinksGenerator implements IIIFResource { * @param format */ public void setFormat(String format) { - this.format = format; + otherContent.setFormat(format); } /** @@ -54,8 +44,7 @@ public class ExternalLinksGenerator implements IIIFResource { * @param label */ public void setLabel(String label) { - propertyValue.setPropertyValue(label); - this.label = propertyValue.getValue(); + otherContent.setLabel(new PropertyValueGenerator().getPropertyValue(label).getValue()); } /** @@ -63,30 +52,11 @@ public class ExternalLinksGenerator implements IIIFResource { * @param type */ public void setType(String type) { - this.type = type; + otherContent.setType(type); } @Override public Resource getResource() { - OtherContent otherContent; - if (format != null) { - otherContent = new OtherContent(identifier, format); - } else { - otherContent = new OtherContent(identifier); - } - if (label != null) { - otherContent.setLabel(label); - } - if (type != null) { - otherContent.setType(type); - } - - // Reset facade properties after creating the resource. - identifier = null; - format = null; - label = null; - type = null; - return otherContent; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java index cac56d0044..7eaaa8cae3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java @@ -16,7 +16,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Facade or Presentation API version 2.1.1 image service property that is added to + * Facade for API version 2.1.1 image service property. Added to * each image resource. */ @Component diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 509a336938..e50229f45a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -22,12 +22,11 @@ import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import de.digitalcollections.iiif.model.sharedcanvas.Sequence; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Facade for the IIIF Presentation API version 2.2.1 domain model. - * * The Manifest is an overall description of the structure and properties of the digital representation * of an object. It carries information needed for the viewer to present the digitized content to the user, * such as a title and other descriptive information about the object or the intellectual work that @@ -38,26 +37,22 @@ import org.springframework.web.context.annotation.RequestScope; @RequestScope public class ManifestGenerator implements IIIFResource { + //private final Manifest manifest; + private String identifier; private String label; - private Resource logo; - private OtherContent seeAlso; - // Becomes the "items" element in IIIF version 3.0 - private CanvasItemsGenerator sequence; - // Renamed to "homepage" in IIIF version 3.0 - private OtherContent related; - // Renamed to "behavior" with IIIF version 3.0 - private ViewingHint viewingHint; - private Resource thumbnail; - private ContentSearchService searchService; private PropertyValue description; - private final List metadata = new ArrayList<>(); - // Renamed to "rights" with IIIF version 3.0 + private ImageContent logo; + private ViewingHint viewingHint; + private Sequence sequence; + private OtherContent seeAlso; + private OtherContent related; + private ImageContent thumbnail; + private ContentSearchService searchService; private final List license = new ArrayList<>(); + private final List metadata = new ArrayList<>(); private List ranges = new ArrayList<>(); - @Autowired - PropertyValueGenerator propertyValue; @Autowired MetadataEntryGenerator metadataEntryGenerator; @@ -74,7 +69,7 @@ public class ManifestGenerator implements IIIFResource { } /** - * Sets the manditory Manifest label. + * Sets the Manifest label. * @param label */ public void setLabel(String label) { @@ -82,7 +77,7 @@ public class ManifestGenerator implements IIIFResource { } public void addLogo(ImageContentGenerator logo) { - this.logo = logo.getResource(); + this.logo = (ImageContent) logo.getResource(); } /** @@ -101,7 +96,7 @@ public class ManifestGenerator implements IIIFResource { * @param sequence */ public void addSequence(CanvasItemsGenerator sequence) { - this.sequence = sequence; + this.sequence = (Sequence) sequence.getResource(); } /** @@ -117,7 +112,7 @@ public class ManifestGenerator implements IIIFResource { * @param thumbnail */ public void addThumbnail(ImageContentGenerator thumbnail) { - this.thumbnail = thumbnail.getResource(); + this.thumbnail = (ImageContent) thumbnail.getResource(); } /** @@ -161,8 +156,7 @@ public class ManifestGenerator implements IIIFResource { * @param value */ public void addDescription(String field, String value) { - propertyValue.setPropertyValue(field, value); - description = propertyValue.getValue(); + description = new PropertyValueGenerator().getPropertyValue(field, value).getValue(); } /** @@ -175,6 +169,7 @@ public class ManifestGenerator implements IIIFResource { @Override public Resource getResource() { + if (identifier == null) { throw new RuntimeException("The Manifest resource requires an identifier."); } @@ -186,11 +181,11 @@ public class ManifestGenerator implements IIIFResource { } if (logo != null) { List logos = new ArrayList<>(); - logos.add((ImageContent) logo); + logos.add(logo); manifest.setLogos(logos); } if (sequence != null) { - manifest.addSequence((Sequence) sequence.getResource()); + manifest.addSequence(sequence); } if (ranges.size() > 0) { manifest.setRanges(ranges); @@ -216,7 +211,7 @@ public class ManifestGenerator implements IIIFResource { manifest.setDescription(description); } if (thumbnail != null) { - manifest.addThumbnail((ImageContent) thumbnail); + manifest.addThumbnail(thumbnail); } if (viewingHint != null) { manifest.addViewingHint(viewingHint); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java index 6b7e079d0b..9939fa9561 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java @@ -14,6 +14,9 @@ import de.digitalcollections.iiif.model.Profile; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +/** + * A profile for an AnnotationList or Service. + */ @Component @Scope("prototype") public class ProfileGenerator implements IIIFValue { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java index af90628dc1..7aa2e9a89e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java @@ -8,24 +8,26 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.PropertyValue; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; /** - * Supports single value PropertyValues. + * Type for strings that are intended to be displayed to the user. + * + *

    Is organized as a mapping of languages to one or more values. See + * http://iiif.io/api/presentation/2.1/#language-of-property-values and + * http://iiif.io/api/presentation/2.1/#html-markup-in-property-values for more information. */ -@Component -@Scope("prototype") public class PropertyValueGenerator implements IIIFValue { private PropertyValue propertyValue; - public void setPropertyValue(String label) { - propertyValue = new PropertyValue(label); + public PropertyValueGenerator getPropertyValue(String val1, String val2) { + propertyValue = new PropertyValue(val1, val2); + return this; } - public void setPropertyValue(String val1, String val2) { - propertyValue = new PropertyValue(val1, val2); + public PropertyValueGenerator getPropertyValue(String val1) { + propertyValue = new PropertyValue(val1); + return this; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java index 4c8c568953..7e9dd745e7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java @@ -53,7 +53,7 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. * Adds canvas to Range canvas list. * @param canvas */ - public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + public void addCanvas(CanvasGenerator canvas) { canvasList.add((Canvas) canvas.getResource()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 6755142557..1959949fe8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired; * Base class for IIIF responses. */ public abstract class AbstractResourceService { - /** * These values are defined in dspace configuration. */ @@ -39,7 +38,6 @@ public abstract class AbstractResourceService { */ protected static String DOCUMENT_VIEWING_HINT; - // TODO: should these bundle settings be added to dspace configuration or hard-coded here? // The DSpace bundle used for IIIF entity types. protected static final String IIIF_BUNDLE = "IIIF"; // The DSpace bundle for other content related to item. @@ -100,12 +98,12 @@ public abstract class AbstractResourceService { * @param mimeType the image mime type * @param bitstreamID the bitstream uuid */ - protected void addImage(CanvasGenerator canvas, String mimeType, UUID bitstreamID) throws - RuntimeException { - canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); - // Add image content resource to canvas facade. - canvas.addImage(getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH).getResource()); - } +// protected void addImage(CanvasGenerator canvas, String mimeType, UUID bitstreamID) throws +// RuntimeException { +// canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); +// // Add image content resource to canvas facade. +// canvas.addImage(getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH).getResource()); +// } /** * A small image that depicts or pictorially represents the resource that @@ -134,7 +132,7 @@ public abstract class AbstractResourceService { * @param path the path component of the identifier * @return */ - private ImageContentGenerator getImageContent(UUID uuid, String mimetype, ProfileGenerator profile, String path) { + protected ImageContentGenerator getImageContent(UUID uuid, String mimetype, ProfileGenerator profile, String path) { imageContent.setFormat(mimetype); imageContent.setIdentifier(IMAGE_SERVICE + uuid + path); imageContent.addService(getImageService(profile, uuid.toString())); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java index 43e7caf653..880ff00dbf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java @@ -14,7 +14,6 @@ import java.util.UUID; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.AnnotationListGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; -import org.dspace.app.rest.iiif.model.generator.PropertyValueGenerator; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; @@ -26,11 +25,16 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; @Component +@RequestScope public class AnnotationListService extends AbstractResourceService { + ApplicationContext applicationContext; + @Autowired IIIFUtils utils; @@ -43,20 +47,13 @@ public class AnnotationListService extends AbstractResourceService { @Autowired BitstreamFormatService bitstreamFormatService; - @Autowired - PropertyValueGenerator propertyValue; - @Autowired AnnotationListGenerator annotationList; - @Autowired - AnnotationGenerator annotation; + public AnnotationListService(ApplicationContext applicationContext, ConfigurationService configurationService) { - @Autowired - ExternalLinksGenerator otherContent; - - public AnnotationListService(ConfigurationService configurationService) { setConfiguration(configurationService); + this.applicationContext = applicationContext; } /** @@ -73,23 +70,19 @@ public class AnnotationListService extends AbstractResourceService { */ public String getSeeAlsoAnnotations(Context context, UUID id) throws RuntimeException { - /** - * We need the DSpace item to proceed. - */ + + // We need the DSpace item to proceed Item item; try { item = itemService.find(context, id); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - /** - * AnnotationList requires an identifier. - */ + // AnnotationList requires an identifier. annotationList.setIdentifier(IIIF_ENDPOINT + id + "/manifest/seeAlso"); - /** - * Get the "OtherContent" bundle for the item. Then add - * Annotations for each bitstream found in the bundle. - */ + + // Get the "OtherContent" bundle for the item. Add + // Annotations for each bitstream found in the bundle. List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); if (bundles.size() > 0) { for (Bundle bundle : bundles) { @@ -103,19 +96,26 @@ public class AnnotationListService extends AbstractResourceService { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - annotation.setIdentifier(IIIF_ENDPOINT + bitstream.getID() + "/annot"); - annotation.setMotivation(AnnotationGenerator.LINKING); - otherContent.setIdentifier(BITSTREAM_PATH_PREFIX - + "/" - + bitstream.getID() - + "/content"); - otherContent.setFormat(mimetype); - otherContent.setLabel(bitstream.getName()); - annotation.setResource(otherContent); + AnnotationGenerator annotation = applicationContext + .getBean(AnnotationGenerator.class, IIIF_ENDPOINT + bitstream.getID() + + "/annot", AnnotationGenerator.LINKING); + annotation.setResource(getLinksGenerator(mimetype, bitstream)); annotationList.addResource(annotation); } } } return utils.asJson(annotationList.getResource()); } + + private ExternalLinksGenerator getLinksGenerator(String mimetype, Bitstream bitstream) { + String identifier = BITSTREAM_PATH_PREFIX + + "/" + + bitstream.getID() + + "/content"; + ExternalLinksGenerator otherContentGenerator = applicationContext + .getBean(ExternalLinksGenerator.class, identifier); + otherContentGenerator.setFormat(mimetype); + otherContentGenerator.setLabel(bitstream.getName()); + return otherContentGenerator; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java index d9e23156be..bdff2a6896 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java @@ -1,2 +1,18 @@ -package org.dspace.app.rest.iiif.service;public interface AnnotionService { +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.util.UUID; + +public interface AnnotationService { + + void initializeQuery(String endpoint, String manifestId, boolean validationEnabled); + + String getSolrSearchResponse(UUID uuid, String query); + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java index 51dfbbb08a..eff51e86f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -19,11 +19,13 @@ import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; /** * Canvases may be dereferenced separately from the manifest via their IDs. */ @Component +@RequestScope public class CanvasLookupService extends AbstractResourceService { @Autowired @@ -48,7 +50,10 @@ public class CanvasLookupService extends AbstractResourceService { String mimeType = utils.getBitstreamMimeType(bitstream, context); CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, canvasPosition); if (mimeType.contains("image/")) { - addImage(canvas, mimeType, bitstreamID); + canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); + canvas.addImage(getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH) + .getResource()); + getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH).getResource(); } return utils.asJson(canvas.getResource()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index 9c7ee3bb91..77fde8ca9e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -11,7 +11,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.services.ConfigurationService; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -24,16 +24,15 @@ public class CanvasService extends AbstractResourceService { // Default canvas dimensions. protected static final Integer DEFAULT_CANVAS_WIDTH = 1200; protected static final Integer DEFAULT_CANVAS_HEIGHT = 1600; - - @Autowired - CanvasGenerator canvas; + ApplicationContext applicationContext; /** * Constructor. * @param configurationService the DSpace configuration service. */ - public CanvasService(ConfigurationService configurationService) { + public CanvasService(ApplicationContext applicationContext, ConfigurationService configurationService) { setConfiguration(configurationService); + this.applicationContext = applicationContext; } /** @@ -47,11 +46,14 @@ public class CanvasService extends AbstractResourceService { * @return canvas object */ protected CanvasGenerator getCanvas(String id, Info info, int count) { + CanvasGenerator canvas = + applicationContext.getBean(CanvasGenerator.class, IIIF_ENDPOINT + id + "/canvas/c" + count); + // canvas.setIdentifier(IIIF_ENDPOINT + id + "/canvas/c" + count); + int pagePosition = count + 1; + String label = "Page " + pagePosition; // Defaults settings. int canvasWidth = DEFAULT_CANVAS_WIDTH; int canvasHeight = DEFAULT_CANVAS_HEIGHT; - int pagePosition = count + 1; - String label = "Page " + pagePosition; // Override with settings from info.json, if available. if (info != null && info.getGlobalDefaults() != null && info.getCanvases() != null) { // Use global settings if activated. @@ -71,10 +73,9 @@ public class CanvasService extends AbstractResourceService { } else { log.info("Correctly formatted info.json was not found for item. Using application defaults."); } - canvas.setIdentifier(IIIF_ENDPOINT + id + "/canvas/c" + count); - canvas.setLabel(label); canvas.setHeight(canvasHeight); canvas.setWidth(canvasWidth); + canvas.setLabel(label); return canvas; } @@ -86,7 +87,8 @@ public class CanvasService extends AbstractResourceService { * @return */ protected CanvasGenerator getRangeCanvasReference(String identifier, String startCanvas) { - canvas.setIdentifier(IIIF_ENDPOINT + identifier + startCanvas); + CanvasGenerator canvas = + applicationContext.getBean(CanvasGenerator.class, IIIF_ENDPOINT + identifier + startCanvas); return canvas; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 1a89d8144f..119846434b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -7,14 +7,10 @@ */ package org.dspace.app.rest.iiif.service; -import java.sql.SQLException; import java.util.List; -import java.util.UUID; -import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; -import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; +import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; @@ -30,20 +26,25 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; /** * Generates IIIF Manifest JSON response for a DSpace Item. */ @Component +@RequestScope public class ManifestService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ManifestService.class); - private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; + // TODO i18n private static final String RELATED_ITEM_LABEL = "DSpace item view"; private static final String SEE_ALSO_LABEL = "More descriptions of this resource"; + private final ApplicationContext applicationContext; + @Autowired protected ItemService itemService; @@ -51,13 +52,7 @@ public class ManifestService extends AbstractResourceService { CanvasService canvasService; @Autowired - ExternalLinksGenerator otherContentGenerator; - - @Autowired - ManifestGenerator manifestGenerator; - - @Autowired - CanvasItemsGenerator sequenceGenerator; + SequenceService sequenceService; @Autowired RangeGenerator rangeGenerator; @@ -68,12 +63,17 @@ public class ManifestService extends AbstractResourceService { @Autowired IIIFUtils utils; + @Autowired + ManifestGenerator manifestGenerator; + + /** * Constructor. * @param configurationService the DSpace configuration service. */ - public ManifestService(ConfigurationService configurationService) { + public ManifestService(ApplicationContext applicationContext, ConfigurationService configurationService) { setConfiguration(configurationService); + this.applicationContext = applicationContext; } /** @@ -84,18 +84,20 @@ public class ManifestService extends AbstractResourceService { * @return Manifest as JSON */ public String getManifest(Item item, Context context) { - initializeManifest(item, context); + populateManifest(item, context); return utils.asJson(manifestGenerator.getResource()); } /** - * Initializes the Manifest for a DSpace item. + * Populates the Manifest for a DSpace Item. * * @param item DSpace Item * @param context DSpace context * @return manifest object */ - private void initializeManifest(Item item, Context context) { + private void populateManifest(Item item, Context context) { + // If an IIIF bundle is found it will be used. Otherwise, + // images in the ORIGINAL bundle will be used. List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); List bitstreams = utils.getBitstreams(bundles); Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); @@ -113,19 +115,15 @@ public class ManifestService extends AbstractResourceService { } /** - * Returns a single sequence with canvases and a rendering property (optional). + * Adds a single sequence with canvases and a rendering property (optional). * @param item DSpace Item * @param bitstreams list of bitstreams * @param context the DSpace context * @return a sequence of canvases */ private void addSequence(Item item, List bitstreams, Context context, Info info) { - sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); - if (bitstreams.size() > 0) { - addCanvases(sequenceGenerator, context, item, bitstreams, info); - } - addRendering(sequenceGenerator, item, context); - manifestGenerator.addSequence(sequenceGenerator); + manifestGenerator.addSequence( + sequenceService.getSequence(item, bitstreams, context, info)); } /** @@ -164,44 +162,13 @@ public class ManifestService extends AbstractResourceService { */ private void addRelated(Item item) { String url = CLIENT_URL + "/items/" + item.getID(); - otherContentGenerator.setIdentifier(url); + ExternalLinksGenerator otherContentGenerator = applicationContext + .getBean(ExternalLinksGenerator.class, url); otherContentGenerator.setFormat("text/html"); otherContentGenerator.setLabel(RELATED_ITEM_LABEL); manifestGenerator.addRelated(otherContentGenerator); } - /** - * This method adds a canvas to the sequence for each item in the list of DSpace bitstreams. - * To be added bitstreams must be on image mime type. - * - * @param sequence the sequence object - * @param context the DSpace context - * @param item the DSpace Item - * @param bitstreams list of DSpace bitstreams - */ - private void addCanvases(CanvasItemsGenerator sequence, Context context, Item item, - List bitstreams, Info info) { - // TODO: This adds all bitstreams from a bundle. Consider bitstream pagination. - /** - * Counter tracks the position of the bitstream in the list and is used to create the canvas identifier. - * Bitstream order is determined by position in the IIIF DSpace bundle. - */ - int counter = 0; - for (Bitstream bitstream : bitstreams) { - UUID bitstreamID = bitstream.getID(); - String mimeType = utils.getBitstreamMimeType(bitstream, context); - if (utils.checkImageMimeType(mimeType)) { - CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, counter); - addImage(canvas, mimeType, bitstreamID); - if (counter == 2) { - addImage(canvas, mimeType, bitstreamID); - } - sequence.addCanvas(canvas); - counter++; - } - } - } - /** * A hint to the client as to the most appropriate method of displaying the resource. * @@ -229,53 +196,13 @@ public class ManifestService extends AbstractResourceService { if (bundles.size() == 0) { return; } - otherContentGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso"); - otherContentGenerator.setType(AnnotationList.TYPE); + ExternalLinksGenerator otherContentGenerator = applicationContext + .getBean(ExternalLinksGenerator.class, IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso"); + otherContentGenerator.setType(AnnotationGenerator.TYPE); otherContentGenerator.setLabel(SEE_ALSO_LABEL); manifestGenerator.addSeeAlso(otherContentGenerator); } - /** - * A link to an external resource intended for display or download by a human user. - * This property can be used to link from a manifest, collection or other resource - * to the preferred viewing environment for that resource, such as a viewer page on - * the publisher’s web site. Other uses include a rendering of a manifest as a PDF - * or EPUB. - * - * This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds - * it to the Sequence if found. - * - * @param sequence Sequence object - * @param item DSpace Item - * @param context DSpace context - */ - private void addRendering(CanvasItemsGenerator sequence, Item item, Context context) { - List bundles = item.getBundles("ORIGINAL"); - if (bundles.size() == 0) { - return; - } - Bundle bundle = bundles.get(0); - List bitstreams = bundle.getBitstreams(); - for (Bitstream bitstream : bitstreams) { - String mimeType = null; - try { - mimeType = bitstream.getFormat(context).getMIMEType(); - } catch (SQLException e) { - e.printStackTrace(); - } - // If the ORIGINAL bundle contains a PDF, assume that it represents the - // item and add to rendering. Ignore other mime-types. This convention should - // be documented. - if (mimeType != null && mimeType.contentEquals("application/pdf")) { - String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; - otherContentGenerator.setIdentifier(id); - otherContentGenerator.setLabel(PDF_DOWNLOAD_LABEL); - otherContentGenerator.setFormat(mimeType); - sequence.addRendering(otherContentGenerator); - } - } - } - /** * A link to a service that makes more functionality available for the resource, * such as the base URI of an associated IIIF Search API service. @@ -288,7 +215,6 @@ public class ManifestService extends AbstractResourceService { private void addSearchService(Item item) { if (utils.isSearchable(item)) { contentSearchGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/search"); - // TODO: get label from configuration then set on generator? manifestGenerator.addService(contentSearchGenerator); } } @@ -310,7 +236,7 @@ public class ManifestService extends AbstractResourceService { } /** - * Sets properties on the RangeFacade. + * Sets properties on the Range. * @param identifier DSpace item id * @param range range from info.json configuration * @param pos list position of the range @@ -339,7 +265,7 @@ public class ManifestService extends AbstractResourceService { } /** - * If the logo is defined in DSpace configuration, add to manifest + * If the logo is defined in DSpace configura */ private void setLogoContainer() { if (IIIF_LOGO_IMAGE != null) { @@ -347,4 +273,5 @@ public class ManifestService extends AbstractResourceService { manifestGenerator.addLogo(imageContent); } } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index 611ac3ba54..fa3d834bb4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -11,6 +11,7 @@ import java.util.UUID; import org.apache.logging.log4j.Logger; import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @@ -22,9 +23,11 @@ import org.springframework.web.context.annotation.RequestScope; public class SearchService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SearchService.class); - private final boolean validationEnabled; + @Autowired + WordHighlightSolrSearch annotationService; + public SearchService(ConfigurationService configurationService) { validationEnabled = configurationService .getBooleanProperty("discovery.solr.url.validation.enabled", true); @@ -38,10 +41,8 @@ public class SearchService extends AbstractResourceService { * @return IIIF search result with page coordinate annotations. */ public String searchWithinManifest(UUID uuid, String query) { - // Support for https://github.com/dbmdz/solr-ocrhighlighting - AnnotationService annotationService = - new WordHighlightSolrSearch(uuid, getManifestId(uuid), IIIF_ENDPOINT, validationEnabled); - return annotationService.getSolrSearchResponse(query); + annotationService.initializeQuery(IIIF_ENDPOINT, getManifestId(uuid), validationEnabled); + return annotationService.getSolrSearchResponse(uuid, query); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java new file mode 100644 index 0000000000..1ac46e313c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java @@ -0,0 +1,125 @@ +package org.dspace.app.rest.iiif.service; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; +import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class SequenceService extends AbstractResourceService { + + // TODO i18n + private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; + + @Autowired + CanvasItemsGenerator sequenceGenerator; + + @Autowired + CanvasService canvasService; + + ApplicationContext applicationContext; + + public SequenceService(ApplicationContext applicationContext, ConfigurationService configurationService) { + setConfiguration(configurationService); + this.applicationContext = applicationContext; + } + + public CanvasItemsGenerator getSequence(Item item, List bitstreams, Context context, Info info) { + + sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); + if (bitstreams.size() > 0) { + addCanvases(context, item, bitstreams, info); + } + addRendering(item, context); + + return sequenceGenerator; + } + + /** + * This method adds a canvas to the sequence for each item in the list of DSpace bitstreams. + * Bitstreams must be on image mime type. + * + * @param context the DSpace context + * @param item the DSpace Item + * @param bitstreams list of DSpace bitstreams + */ + private void addCanvases(Context context, Item item, + List bitstreams, Info info) { + /* + * The counter tracks the position of the bitstream in the list and is used to create the canvas identifier. + * The order of bitstreams (and thus page order in documents) is determined by position in the DSpace + * bundle. + */ + int counter = 0; + for (Bitstream bitstream : bitstreams) { + UUID bitstreamID = bitstream.getID(); + String mimeType = utils.getBitstreamMimeType(bitstream, context); + if (utils.checkImageMimeType(mimeType)) { + CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, counter); + ImageContentGenerator imageGenerator = + getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH); + canvas.addImage(imageGenerator.getResource()); + canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); + sequenceGenerator.addCanvas(canvas); + counter++; + } + } + } + + /** + * A link to an external resource intended for display or download by a human user. + * This property can be used to link from a manifest, collection or other resource + * to the preferred viewing environment for that resource, such as a viewer page on + * the publisher’s web site. Other uses include a rendering of a manifest as a PDF + * or EPUB. + * + * This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds + * it to the Sequence if found. + * + * @param item DSpace Item + * @param context DSpace context + */ + private void addRendering(Item item, Context context) { + List bundles = item.getBundles("ORIGINAL"); + if (bundles.size() == 0) { + return; + } + Bundle bundle = bundles.get(0); + List bitstreams = bundle.getBitstreams(); + for (Bitstream bitstream : bitstreams) { + String mimeType = null; + try { + mimeType = bitstream.getFormat(context).getMIMEType(); + } catch (SQLException e) { + e.printStackTrace(); + } + // If the ORIGINAL bundle contains a PDF, assume that it represents the + // item and add to rendering. Ignore other mime-types. Other options + // might be using the primary bitstream or relying on a bitstream metadata + // field, e.g. iiif.rendering + if (mimeType != null && mimeType.contentEquals("application/pdf")) { + String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; + ExternalLinksGenerator otherContentGenerator = applicationContext + .getBean(ExternalLinksGenerator.class, id); + otherContentGenerator.setLabel(PDF_DOWNLOAD_LABEL); + otherContentGenerator.setFormat(mimeType); + sequenceGenerator.addRendering(otherContentGenerator); + } + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java index 3898b1a84e..89d91d66a3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java @@ -1,2 +1,264 @@ -package org.dspace.app.rest.iiif.service;public class WordHighlightAltoSearch { +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.iiif.service; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.commons.validator.routines.UrlValidator; +import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.impl.NoOpResponseParser; +import org.apache.solr.client.solrj.request.QueryRequest; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.util.NamedList; +import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; +import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; +import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.discovery.SolrSearchCore; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * Support for https://github.com/dbmdz/solr-ocrhighlighting + */ +@Component +@RequestScope +public class WordHighlightSolrSearch implements AnnotationService { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(WordHighlightSolrSearch.class); + + private final ApplicationContext applicationContext; + private String endpoint; + private String manifestId; + private boolean validationEnabled; + + @Autowired + IIIFUtils utils; + + @Autowired + ContentAsTextGenerator contentAsText; + + @Autowired + SearchResultGenerator searchResult; + + @Autowired + SolrSearchCore solrSearchCore; + + + public WordHighlightSolrSearch(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public void initializeQuery(String endpoint, String manifestId, boolean validationEnabled) { + this.validationEnabled = validationEnabled; + this.endpoint = endpoint; + this.manifestId = manifestId; + } + + /** + * Executes the Search API solr query. + * @param query encoded query terms + * + * @return json query response + */ + @Override + public String getSolrSearchResponse(UUID uuid, String query) { + String json = ""; + String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("iiif.solr.search.url"); + UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); + if (urlValidator.isValid(solrService) || this.validationEnabled) { + HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); + solrServer.setUseMultiPartPost(true); + SolrQuery solrQuery = getSolrQuery(adjustQuery(query), manifestId); + QueryRequest req = new QueryRequest(solrQuery); + // returns raw json response. + req.setResponseParser(new NoOpResponseParser("json")); + NamedList resp; + try { + resp = solrServer.request(req); + json = (String) resp.get("response"); + } catch (SolrServerException | IOException e) { + throw new RuntimeException("Unable to retrieve search response.", e); + } + } else { + log.error("Error while initializing solr, invalid url: " + solrService); + } + return getAnnotationList(uuid, json, query); + } + + /** + * Wraps multi-word queries in parens. + * @param query the search query + * @return + */ + private String adjustQuery(String query) { + if (query.split(" ").length > 1) { + return '(' + query + ')'; + } + return query; + } + + /** + * Constructs a solr search URL. + * + * @param query the search terms + * @param manifestId the id of the manifest in which to search + * @return solr query + */ + private SolrQuery getSolrQuery(String query, String manifestId) { + SolrQuery solrQuery = new SolrQuery(); + solrQuery.set("q", "ocr_text:" + query + " AND manifest_url:\"" + manifestId + "\""); + solrQuery.set(CommonParams.WT, "json"); + solrQuery.set("hl", "true"); + solrQuery.set("hl.ocr.fl", "ocr_text"); + solrQuery.set("hl.ocr.contextBlock", "line"); + solrQuery.set("hl.ocr.contextSize", "2"); + solrQuery.set("hl.snippets", "10"); + solrQuery.set("hl.ocr.trackPages", "off"); + solrQuery.set("hl.ocr.limitBlock","page"); + solrQuery.set("hl.ocr.absoluteHighlights", "true"); + + return solrQuery; + } + + /** + * Generates a Search API response from the word_highlighting solr query response. + * + * The function assumes that the solr query responses contains page IDs + * (taken from the ALTO Page ID element) in the following format: + * Page.0, Page.1, Page.2.... + * + * The identifier values must be aligned with zero-based IIIF canvas identifiers: + * c0, c1, c2.... + * + * The convention convention for Alto IDs must be followed when indexing ALTO files + * into the word_highlighting solr index. If it is not, search responses will not + * match canvases. + * + * @param json solr search result + * @param query the solr query + * @return a search response in JSON + */ + private String getAnnotationList(UUID uuid, String json, String query) { + searchResult.setIdentifier(manifestId + "/search?q=" + + URLEncoder.encode(query, StandardCharsets.UTF_8)); + GsonBuilder builder = new GsonBuilder(); + Gson gson = builder.create(); + JsonObject body = gson.fromJson(json, JsonObject.class); + if (body == null) { + log.warn("Unable to process json response."); + return utils.asJson(searchResult.getResource()); + } + // outer ocr highlight element + JsonObject highs = body.getAsJsonObject("ocrHighlighting"); + // highlight entries + for (Map.Entry ocrIds: highs.entrySet()) { + // ocr_text + JsonObject ocrObj = ocrIds.getValue().getAsJsonObject().getAsJsonObject("ocr_text"); + // snippets array + if (ocrObj != null) { + for (JsonElement snippetArray : ocrObj.getAsJsonObject().get("snippets").getAsJsonArray()) { + String pageId = getCanvasId(snippetArray.getAsJsonObject().get("pages")); + for (JsonElement highlights : snippetArray.getAsJsonObject().getAsJsonArray("highlights")) { + for (JsonElement highlight : highlights.getAsJsonArray()) { + searchResult.addResource(getAnnotation(highlight, pageId, uuid)); + } + } + } + } + } + + return utils.asJson(searchResult.getResource()); + } + + /** + * Returns the annotation generator for the highlight. + * @param highlight highlight element from solor response + * @param pageId page id from solr response + * @return generator for a single annotation + */ + private AnnotationGenerator getAnnotation(JsonElement highlight, String pageId, UUID uuid) { + JsonObject hcoords = highlight.getAsJsonObject(); + String text = (hcoords.get("text").getAsString()); + int ulx = hcoords.get("ulx").getAsInt(); + int uly = hcoords.get("uly").getAsInt(); + int lrx = hcoords.get("lrx").getAsInt(); + int lry = hcoords.get("lry").getAsInt(); + String w = Integer.toString(lrx - ulx); + String h = Integer.toString(lry - uly); + String params = ulx + "," + uly + "," + w + "," + h; + return createSearchResultAnnotation(params, text, pageId, uuid); + } + + /** + * Returns position of canvas by extracting from the pages id element. + * @param element the pages element + * @return canvas id + */ + private String getCanvasId(JsonElement element) { + JsonArray pages = element.getAsJsonArray(); + JsonObject page = pages.get(0).getAsJsonObject(); + String[] identArr = page.get("id").getAsString().split("\\."); + // the canvas id. + return "c" + identArr[1]; + } + + /** + * Creates annotation with word highlight coordinates. + * + * @param params word coordinate parameters used for highlighting. + * @param text word text + * @param pageId the page id returned by solr + * @param uuid the dspace item identifier + * @return a single annotation object that contains word highlights on a single page (canvas) + */ + private AnnotationGenerator createSearchResultAnnotation(String params, String text, String pageId, UUID uuid) { + String identifier = this.endpoint + uuid + "/annot/" + pageId + "-" + params; + AnnotationGenerator annotation = applicationContext + .getBean(AnnotationGenerator.class, identifier, AnnotationGenerator.PAINTING); + CanvasGenerator canvas = + applicationContext.getBean(CanvasGenerator.class, identifier); + annotation.setOnCanvas(canvas); + contentAsText.setText(text); + annotation.setResource(contentAsText); + annotation.setWithin(getWithinManifest()); + return annotation; + } + + private List getWithinManifest() { + List withinList = new ArrayList<>(); + ManifestGenerator manifestGenerator = applicationContext.getBean(ManifestGenerator.class, manifestId); + manifestGenerator.setLabel("Search within manifest."); + withinList.add(manifestGenerator); + return withinList; + + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 2dfe7dc49e..73dc8556a1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -41,9 +41,9 @@ public class IIIFUtils { // The canvas position will be appended to this string. private static final String CANVAS_PATH_BASE = "/canvas/c"; - // get dbmdz module subclass. + // get module subclass. protected SimpleModule iiifModule = ObjectMapperFactory.getIiifModule(); - // Use the dbmdz object mapper subclass. + // Use the object mapper subclass. protected ObjectMapper mapper = ObjectMapperFactory.getIiifObjectMapper(); @Autowired @@ -62,11 +62,12 @@ public class IIIFUtils { .filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_type")) .anyMatch(m -> m.getValue().contentEquals("IIIF") || m.getValue().contentEquals("IIIFSearchable")); - List bundles; + List bundles = new ArrayList<>(); if (iiif) { bundles = item.getBundles(iiifBundle); - } else { - bundles = item.getBundles(); + if (bundles.size() == 0) { + bundles = item.getBundles("ORIGINAL"); + } } return bundles; } From 08ea61f654d3739edb3d72dd15a424ecda1b684a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 3 Sep 2021 13:43:16 -0700 Subject: [PATCH 0193/1254] Code cleanup with comments and missing license header. --- .../rest/iiif/model/ObjectMapperFactory.java | 10 +++++---- .../model/generator/BehaviorGenerator.java | 4 ++-- .../iiif/model/generator/CanvasGenerator.java | 3 --- .../model/generator/CanvasItemsGenerator.java | 18 ++++++++-------- .../model/generator/ManifestGenerator.java | 1 - .../iiif/service/AbstractResourceService.java | 21 ------------------- ...vice.java => SearchAnnotationService.java} | 2 +- .../rest/iiif/service/SequenceService.java | 7 +++++++ .../iiif/service/WordHighlightSolrSearch.java | 3 +-- 9 files changed, 26 insertions(+), 43 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/{AnnotationService.java => SearchAnnotationService.java} (91%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java index a5d8aa63ae..f36ed105da 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java @@ -17,16 +17,18 @@ public class ObjectMapperFactory { private ObjectMapperFactory() {} /** - * Gets the jackson ObjectMapper with iiif configuration. - * @return + * Gets the jackson ObjectMapper with dbmdz configuration. + * https://github.com/dbmdz/iiif-apis/blob/main/src/main/java/de/digitalcollections/iiif/model/jackson/IiifObjectMapper.java + * @return jackson mapper */ public static ObjectMapper getIiifObjectMapper() { return new IiifObjectMapper(); } /** - * Gets the jackson SimpleModule with iiif configuration. - * @return + * Gets the jackson SimpleModule with dbmdz configuration. + * https://github.com/dbmdz/iiif-apis/blob/main/src/main/java/de/digitalcollections/iiif/model/jackson/IiifModule.java + * @return model */ public static SimpleModule getIiifModule() { return new IiifModule(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java index 7ea76e8026..e11e15d1ab 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java @@ -11,8 +11,8 @@ import de.digitalcollections.iiif.model.enums.ViewingHint; import org.springframework.stereotype.Component; /** - * IIIF Presentation API 2.1.1 ViewingHint is a hint to the client as to the most appropriate method of - * displaying the resource. This class wraps the ViewingHint enum from the domain model. + * API 2.1.1 ViewingHint is a hint to the client that suggests the appropriate method of + * displaying the resource. * * With IIIF Presentation API 3.0 the viewingHint property is renamed to "behavior". */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index 19a9a3d894..c1299df173 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -76,9 +76,6 @@ public class CanvasGenerator implements IIIFResource { */ @Override public Resource getResource() { -// if (canvas.getHeight() == null || canvas.getWidth() == null) { -// throw new RuntimeException("The Canvas resource requires both height and width dimensions."); -// } return canvas; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java index fb457314c7..e0aa59ff7e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java @@ -7,16 +7,16 @@ */ package org.dspace.app.rest.iiif.model.generator; - import java.util.ArrayList; - import java.util.List; +import java.util.ArrayList; +import java.util.List; - import de.digitalcollections.iiif.model.OtherContent; - import de.digitalcollections.iiif.model.sharedcanvas.Canvas; - import de.digitalcollections.iiif.model.sharedcanvas.Resource; - import de.digitalcollections.iiif.model.sharedcanvas.Sequence; - import org.springframework.beans.factory.annotation.Autowired; - import org.springframework.stereotype.Component; - import org.springframework.web.context.annotation.RequestScope; +import de.digitalcollections.iiif.model.OtherContent; +import de.digitalcollections.iiif.model.sharedcanvas.Canvas; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import de.digitalcollections.iiif.model.sharedcanvas.Sequence; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; /** * Facade for the current Presentation API version 2.1.1 domain model's Sequence class. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index e50229f45a..2efbeb99e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -22,7 +22,6 @@ import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import de.digitalcollections.iiif.model.sharedcanvas.Sequence; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 1959949fe8..ec979bf8cf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.iiif.service; import java.util.UUID; -import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator; import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; @@ -85,26 +84,6 @@ public abstract class AbstractResourceService { return IIIF_ENDPOINT + uuid + "/manifest"; } - /** - * Association of images with their respective canvases is done via annotations. - * Only the annotations that associate images or parts of images are included in - * the canvas in the images property. If a IIIF Image API service is available for - * the image, then a link to the service’s base URI should be included. - * - * This method adds an image annotations to a canvas for both thumbnail and full size - * images. The annotation references the IIIF image service. - * - * @param canvas the Canvas object. - * @param mimeType the image mime type - * @param bitstreamID the bitstream uuid - */ -// protected void addImage(CanvasGenerator canvas, String mimeType, UUID bitstreamID) throws -// RuntimeException { -// canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); -// // Add image content resource to canvas facade. -// canvas.addImage(getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH).getResource()); -// } - /** * A small image that depicts or pictorially represents the resource that * the property is attached to, such as the title page, a significant image diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java similarity index 91% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java index bdff2a6896..da113555cb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.iiif.service; import java.util.UUID; -public interface AnnotationService { +public interface SearchAnnotationService { void initializeQuery(String endpoint, String manifestId, boolean validationEnabled); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java index 1ac46e313c..6b61ff98f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.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.iiif.service; import java.sql.SQLException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java index 89d91d66a3..0c4f617ff8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java @@ -39,7 +39,6 @@ import org.dspace.discovery.SolrSearchCore; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @@ -48,7 +47,7 @@ import org.springframework.web.context.annotation.RequestScope; */ @Component @RequestScope -public class WordHighlightSolrSearch implements AnnotationService { +public class WordHighlightSolrSearch implements SearchAnnotationService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(WordHighlightSolrSearch.class); From 7f4f91e979871d18452fd80987b053fa77186455 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 3 Sep 2021 13:44:32 -0700 Subject: [PATCH 0194/1254] Updated IT. --- .../test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java index 874bc6ed6b..d06573a258 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java @@ -430,6 +430,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withEntityType("IIIF") .build(); String bitstreamContent = "ThisIsSomeDummyText"; From 9dd3026c05ff03aec9264c5109c61414ce8ada2d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 3 Sep 2021 16:58:06 -0400 Subject: [PATCH 0195/1254] Missing commit broke tests. Tighten up code, #2129 --- .../dspace/app/requestitem/RequestItem.java | 7 +- .../requestitem/RequestItemServiceImpl.java | 15 +- .../service/RequestItemService.java | 11 + .../dspace/builder/RequestItemBuilder.java | 34 ++- .../app/rest/RequestItemRepositoryIT.java | 217 ++++++++---------- 5 files changed, 145 insertions(+), 139 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java index 8a77a591b7..9e675e97a7 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java @@ -33,7 +33,6 @@ import org.dspace.core.ReloadableEntity; @Table(name = "requestitem") public class RequestItem implements ReloadableEntity { - @Id @Column(name = "requestitem_id") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "requestitem_seq") @@ -54,8 +53,6 @@ public class RequestItem implements ReloadableEntity { @Column(name = "request_name", length = 64) private String reqName; - // @Column(name = "request_message") -// @Lob @Column(name = "request_message", columnDefinition = "text") private String reqMessage; @@ -82,8 +79,8 @@ public class RequestItem implements ReloadableEntity { /** * Protected constructor, create object using: - * {@link org.dspace.app.requestitem.service.RequestItemService#createRequest(Context, Bitstream, Item, - * boolean, String, String, String)} + * {@link org.dspace.app.requestitem.service.RequestItemService#createRequest( + * Context, Bitstream, Item, boolean, String, String, String)} */ protected RequestItem() { } 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 ebcb7e3dee..ac032718fb 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 @@ package org.dspace.app.requestitem; import java.sql.SQLException; import java.util.Date; +import java.util.List; import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.dao.RequestItemDAO; @@ -16,19 +17,21 @@ import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; +import org.dspace.core.LogManager; import org.dspace.core.Utils; import org.springframework.beans.factory.annotation.Autowired; /** * Service implementation for the RequestItem object. - * This class is responsible for all business logic calls for the RequestItem object and is autowired by Spring. + * This class is responsible for all business logic calls for the RequestItem + * object and is autowired by Spring. * This class should never be accessed directly. * * @author kevinvandevelde at atmire.com */ public class RequestItemServiceImpl implements RequestItemService { - private final Logger log = org.apache.logging.log4j.LogManager.getLogger(RequestItemServiceImpl.class); + private final Logger log = org.apache.logging.log4j.LogManager.getLogger(); @Autowired(required = true) protected RequestItemDAO requestItemDAO; @@ -58,6 +61,12 @@ public class RequestItemServiceImpl implements RequestItemService { return requestItem.getToken(); } + @Override + public List findAll(Context context) + throws SQLException { + return requestItemDAO.findAll(context, RequestItem.class); + } + @Override public RequestItem findByToken(Context context, String token) { try { @@ -79,6 +88,8 @@ public class RequestItemServiceImpl implements RequestItemService { @Override public void delete(Context context, RequestItem requestItem) { + log.debug(LogManager.getHeader(context, "delete_itemrequest", "request_id={}"), + requestItem.getID()); try { requestItemDAO.delete(context, requestItem); } catch (SQLException e) { 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 9d274d1eb9..d8493125ba 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.List; import org.dspace.app.requestitem.RequestItem; import org.dspace.content.Bitstream; @@ -41,6 +42,16 @@ public interface RequestItemService { boolean allFiles, String reqEmail, String reqName, String reqMessage) throws SQLException; + /** + * Fetch all item requests. + * + * @param context current DSpace session. + * @return all item requests. + * @throws java.sql.SQLException passed through. + */ + public List findAll(Context context) + throws SQLException; + /** * Retrieve a request by its token. * diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index 6c19448c1a..ce908851ab 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -42,11 +42,16 @@ public class RequestItemBuilder public void cleanup() throws Exception { LOG.debug("cleanup()"); - if (null != requestItem) { - delete(context, requestItem); - requestItem = null; - } else { - LOG.debug("nothing to clean up."); + try ( Context ctx = new Context(); ) { + ctx.turnOffAuthorisationSystem(); + requestItem = ctx.reloadEntity(requestItem); + if (null != requestItem) { + delete(ctx, requestItem); + ctx.complete(); + requestItem = null; + } else { + LOG.debug("nothing to clean up."); + } } } @@ -72,6 +77,13 @@ public class RequestItemBuilder @Override public RequestItem build() { + LOG.atDebug() + .withLocation() + .log("Building request with item ID {}", + () -> requestItem.getItem().getID().toString()); + System.out.format("Building request with item ID %s%n", + requestItem.getItem().getID().toString()); + new Throwable().printStackTrace(System.out); // Nothing to build. return requestItem; } @@ -85,12 +97,22 @@ public class RequestItemBuilder /** * Delete a request identified by its token. If no such token is known, * simply return. + * * @param token the token identifying the request. + * @throws java.sql.SQLException passed through */ - static public void deleteRequestItem(String token) { + static public void deleteRequestItem(String token) + throws SQLException { + LOG.atDebug() + .withLocation() + .log("Delete RequestItem with token {}", token); try (Context context = new Context()) { RequestItem request = requestItemService.findByToken(context, token); + if (null == request) { + return; + } requestItemService.delete(context, request); + context.complete(); } } 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 be95219add..94e29592a6 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 @@ -27,6 +27,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.Cookie; import com.fasterxml.jackson.databind.ObjectMapper; @@ -68,11 +69,39 @@ public class RequestItemRepositoryIT @Autowired(required = true) RequestItemService requestItemService; + private Collection collection; + + private Item item; + + private Bitstream bitstream; + @Before - public void init() { + public void init() + throws SQLException, AuthorizeException, IOException { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context).withName( - "Parent Community").build(); + context.setCurrentUser(eperson); + + parentCommunity = CommunityBuilder + .createCommunity(context) + .withName("Community") + .build(); + + collection = CollectionBuilder + .createCollection(context, parentCommunity) + .withName("Collection") + .build(); + + item = ItemBuilder + .createItem(context, collection) + .withTitle("Item") + .build(); + + InputStream is = new ByteArrayInputStream(new byte[0]); + bitstream = BitstreamBuilder + .createBitstream(context, item, is) + .withName("Bitstream") + .build(); + context.restoreAuthSystemState(); } @@ -86,24 +115,11 @@ public class RequestItemRepositoryIT throws Exception { System.out.println("findOne (authenticated)"); - context.turnOffAuthorisationSystem(); - - // Create necessary supporting objects. - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .build(); - Item item = ItemBuilder.createItem(context, collection) - .build(); - InputStream is = new ByteArrayInputStream(new byte[0]); - Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) - .build(); - // Create a request. RequestItem request = RequestItemBuilder .createRequestItem(context, item, bitstream) .build(); - context.restoreAuthSystemState(); - // Test: can we find it? String authToken = getAuthToken(admin.getEmail(), password); final String uri = URI_ROOT + '/' + request.getToken(); @@ -124,24 +140,11 @@ public class RequestItemRepositoryIT throws Exception { System.out.println("findOne (not authenticated)"); - context.turnOffAuthorisationSystem(); - - // Create necessary supporting objects. - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .build(); - Item item = ItemBuilder.createItem(context, collection) - .build(); - InputStream is = new ByteArrayInputStream(new byte[0]); - Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) - .build(); - // Create a request. RequestItem request = RequestItemBuilder .createRequestItem(context, item, bitstream) .build(); - context.restoreAuthSystemState(); - // Test: can we find it? final String uri = URI_ROOT + '/' + request.getToken(); getClient().perform(get(uri)) @@ -161,21 +164,7 @@ public class RequestItemRepositoryIT @Test public void testCreateAndReturnAuthenticated() throws SQLException, AuthorizeException, IOException, Exception { - System.out.println("createAndReturn"); - - // Create some necessary objects. - context.turnOffAuthorisationSystem(); - - Collection col = CollectionBuilder.createCollection(context, - parentCommunity).build(); - Item item = ItemBuilder.createItem(context, col).build(); - InputStream is = new ByteArrayInputStream(new byte[0]); - Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) - .withName("/dev/null") - .withMimeType("text/plain") - .build(); - - context.restoreAuthSystemState(); + System.out.println("createAndReturn (authenticated)"); // Fake up a request in REST form. RequestItemRest rir = new RequestItemRest(); @@ -189,28 +178,34 @@ public class RequestItemRepositoryIT // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(is(emptyOrNullString()))), - hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), - hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), - hasJsonPath("$.allfiles", is(false)), - hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), // TODO should be an ISO datetime - hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) - ))) - .andDo((var result) -> saveToken(read(result.getResponse().getContentAsString(), "token"))) - .andReturn(); + AtomicReference requestTokenRef = new AtomicReference<>(); + try { + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(is(emptyOrNullString()))), + hasJsonPath("$.type", is(RequestItemRest.NAME)), + hasJsonPath("$.token", not(is(emptyOrNullString()))), + hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), + hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), + hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), + hasJsonPath("$.allfiles", is(false)), + // TODO should be an ISO datetime + hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), + hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) + ))) + .andDo((var result) -> requestTokenRef.set( + read(result.getResponse().getContentAsString(), "token"))) + .andReturn(); - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestToken); + } finally { + // Clean up the created request. + RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); + } } /** @@ -224,21 +219,7 @@ public class RequestItemRepositoryIT @Test public void testCreateAndReturnNotAuthenticated() throws SQLException, AuthorizeException, IOException, Exception { - System.out.println("createAndReturn"); - - // Create some necessary objects. - context.turnOffAuthorisationSystem(); - - Collection col = CollectionBuilder.createCollection(context, - parentCommunity).build(); - Item item = ItemBuilder.createItem(context, col).build(); - InputStream is = new ByteArrayInputStream(new byte[0]); - Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) - .withName("/dev/null") - .withMimeType("text/plain") - .build(); - - context.restoreAuthSystemState(); + System.out.println("createAndReturn (not authenticated)"); // Fake up a request in REST form. RequestItemRest rir = new RequestItemRest(); @@ -251,27 +232,32 @@ public class RequestItemRepositoryIT // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); - getClient().perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(is(emptyOrNullString()))), - hasJsonPath("$.type", is(RequestItemRest.NAME)), - hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), - hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), - hasJsonPath("$.allfiles", is(false)), - hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), // TODO should be an ISO datetime - hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) - ))) - .andDo((var result) -> saveToken(read(result.getResponse().getContentAsString(), "token"))) - .andReturn(); - - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestToken); + AtomicReference requestTokenRef = new AtomicReference<>(); + try { + getClient().perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(is(emptyOrNullString()))), + hasJsonPath("$.type", is(RequestItemRest.NAME)), + hasJsonPath("$.token", not(is(emptyOrNullString()))), + hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), + hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), + hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), + hasJsonPath("$.allfiles", is(false)), + // TODO should be an ISO datetime + hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), + hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) + ))) + .andDo((var result) -> requestTokenRef.set( + read(result.getResponse().getContentAsString(), "token"))) + .andReturn(); + } finally { + // Clean up the created request. + RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); + } } /** @@ -284,6 +270,8 @@ public class RequestItemRepositoryIT @Test public void testCreateWithInvalidCSRF() throws Exception { + System.out.println("testCreateWithInvalidCSRF"); + // Login via password to retrieve a valid token String token = getAuthToken(eperson.getEmail(), password); @@ -294,20 +282,6 @@ public class RequestItemRepositoryIT Cookie[] cookies = new Cookie[1]; cookies[0] = new Cookie(AUTHORIZATION_COOKIE, token); - // Create some necessary objects. - context.turnOffAuthorisationSystem(); - - Collection col = CollectionBuilder.createCollection(context, - parentCommunity).build(); - Item item = ItemBuilder.createItem(context, col).build(); - InputStream is = new ByteArrayInputStream(new byte[0]); - Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) - .withName("/dev/null") - .withMimeType("text/plain") - .build(); - - context.restoreAuthSystemState(); - // Fake up a request in REST form. RequestItemRest rir = new RequestItemRest(); rir.setBitstreamId(bitstream.getID().toString()); @@ -346,6 +320,8 @@ public class RequestItemRepositoryIT @Test public void testUntrustedOrigin() throws Exception { + System.out.println("testUntrustedOrigin"); + // First, get a valid login token String token = getAuthToken(eperson.getEmail(), password); @@ -379,15 +355,4 @@ public class RequestItemRepositoryIT Class instanceClass = instance.getDomainClass(); assertEquals("Wrong domain class", RequestItemRest.class, instanceClass); } - - /** Saves the request token generated by creating an item request. */ - private String requestToken; - - /** - * Silly work-around because lambdas can't mutate external variables. - * @param token a request token to be remembered. - */ - private void saveToken(String token) { - requestToken = token; - } } From 6931b3ef3173c297159563a6257a7587afc18644 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 3 Sep 2021 14:11:08 -0700 Subject: [PATCH 0196/1254] Minor comment update. --- .../dspace/app/rest/iiif/model/generator/CanvasGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index c1299df173..5de0a05b75 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -17,8 +17,8 @@ import org.springframework.stereotype.Component; /** * Presentation API version 2.1.1 Canvas model. * - * Changes a Presentation API version 3.0 will likely require new fields for - * this class to support multiple media types. + * Changes a Presentation API version 3.0 will likely require updates for + * multiple media types, etc. */ @Component @Scope("prototype") From abad6116cb770e2d2a0b01019612446905573961 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sat, 4 Sep 2021 21:24:16 +0300 Subject: [PATCH 0197/1254] DS-4587: Update the spider user agent file This updates the included spider user agent file to the latest from the COUNTER-Robots project. DSpace's own copy is over five years old and is missing a bunch of new patterns, which greatly decreases the accuracy of the Solr usage statistics. Port of #3333 to main for DSpace 7.x. See: https://jira.lyrasis.org/browse/DS-4587 See: https://github.com/atmire/COUNTER-Robots/releases/tag/2021-07-05 --- dspace/config/spiders/agents/example | 332 ++++++++++++++++----------- 1 file changed, 202 insertions(+), 130 deletions(-) diff --git a/dspace/config/spiders/agents/example b/dspace/config/spiders/agents/example index 1e6749e277..f206558d81 100644 --- a/dspace/config/spiders/agents/example +++ b/dspace/config/spiders/agents/example @@ -1,235 +1,307 @@ -Mozilla/5\.0 \(compatible; PRTG Network Monitor \(www\.paessler\.com\); Windows\) -Mozilla/5\.0 \(compatible; monitis \- premium monitoring service; http://www\.monitis\.com\) -Pingdom\.com_bot_version_1\.4_\(http://www\.pingdom\.com/\) +bot +^Buck\/[0-9] +spider +crawl +^.?$ +[^a]fish +^IDA$ +^ruby$ +^@ozilla\/\d +^脝脝陆芒潞贸碌脛$ +^破解后的$ +AddThis +A6-Indexer +ADmantX +alexa Alexandria(\s|\+)prototype(\s|\+)project AllenTrack -Arachmo -Brutus\/AET -China\sLocal\sBrowse\s2\.6 -Code\sSample\sWeb\sClient -ContentSmartz -DSurf -DataCha0s\/2\.0 -Demo\sBot -EmailSiphon -EmailWolf -FDM(\s|\+)1 -Fetch(\s|\+)API(\s|\+)Request -GetRight -Goldfire(\s|\+)Server -Googlebot -HTTrack -LOCKSS -LWP\:\:Simple -MSNBot -Microsoft(\s|\+)URL(\s|\+)Control -Milbot -MuscatFerre -NABOT -NaverBot -Offline(\s|\+)Navigator -OurBrowser -Python\-urllib -Readpaper -Strider -T\-H\-U\-N\-D\-E\-R\-S\-T\-O\-N\-E -Teleport(\s|\+)Pro -Teoma -Wanadoo -Web(\s|\+)Downloader -WebCloner -WebCopier -WebReaper -WebStripper -WebZIP -Webinator -Webmetrics -Wget -Xenu(\s|\+)Link(\s|\+)Sleuth -[+:,\.\;\/\\-]bot -[^a]fish -^voyager\/ -acme\.spider -alexa almaden appie +API[\+\s]scraper +Arachni +Arachmo architext -archive\.org_bot +ArchiveTeam +aria2\/\d arks +^Array$ asterias atomz -autoemailspider -awbot -baiduspider -bbot -biadu +BDFetch +Betsie +baidu biglotron +BingPreview +binlar bjaaland -blaiz\-bee +Blackboard[\+\s]Safeassign +blaiz-bee bloglines blogpulse -boitho\.com\-dc -bookmark\-manager -bot -bot[+:,\.\;\/\\-] -bspider +boitho\.com-dc +bookmark-manager +Brutus\/AET +BUbiNG bwh3_user_agent +CakePHP celestial -cfnetwork|checkbot +cfnetwork +checklink +checkprivacy +China\sLocal\sBrowse\s2\.6 +Citoid +cloakDetect +coccoc\/1\.0 +Code\sSample\sWeb\sClient +ColdFusion +collection@infegy.com +com\.plumanalytics combine -commons\-httpclient contentmatch +ContentSmartz +convera core -crawl -crawler +Cortana +CoverScout +crusty\/\d +curl\/ cursor custo -daumoa +DataCha0s\/2\.0 +daum(oa)? +^\%?default\%?$ +DeuSu\/ +Dispatch\/\d +Docoloc docomo -dtSearchSpider -dumbot +Download\+Master +Drupal +DSurf +DTS Agent +EasyBib[\+\s]AutoCite[\+\s] easydl -exabot -fast-webcrawler +EBSCO\sEJS\sContent\sServer +EcoSearch +ELinks\/ +EmailSiphon +EmailWolf +Embedly +EThOS\+\(British\+Library\) +facebookexternalhit\/ favorg +FDM(\s|\+)\d +Feedbin feedburner -feedfetcher\-google +FeedFetcher +feedreader ferret +Fetch(\s|\+)API(\s|\+)Request findlinks -gaisbot +findthatfile +^FileDown$ +^Filter$ +^firefox$ +^FOCA +Fulltext +Funnelback +Genieo +GetRight geturl -gigabot -girafabot -gnodspider +GigablastOpenSource +G-i-g-a-b-o-t +GLMSLinkAnalysis +Goldfire(\s|\+)Server google +Grammarly grub gulliver +gvfs\/ harvest heritrix -hl_ftien_spider holmes htdig htmlparser -httpget\-5\.2\.2 -httpget\?5\.2\.2 +HttpComponents\/1.1 +HTTPFetcher +http.?client +httpget httrack -iSiloX ia_archiver ichiro iktomi ilse +Indy Library +^integrity\/\d internetseer intute -java -java\/ +iSiloX +iskanie +^java\/\d{1,2}.\d jeeves +Jersey\/\d jobo kyluka larbin +libcurl +libhttp libwww -libwww\-perl lilina -linkbot -linkcheck -linkchecker +^LinkAnalyser +link.?check +LinkLint-checkonly +^LinkParser\/ +^LinkSaver\/ linkscan +LinkTiger linkwalker +lipperhey livejournal\.com -lmspider +LOCKSS +LongURL.API +ltx71 lwp -lwp\-request -lwp\-tivial -lwp\-trivial lycos[_+] -mail.ru -mediapartners\-google +mail\.ru +MarcEdit +mediapartners-google megite -milbot +MetaURI[\+\s]API\/\d\.\d +Microsoft(\s|\+)URL(\s|\+)Control +Microsoft Office Existence Discovery +Microsoft Office Protocol Discovery +Microsoft-WebDAV-MiniRedir mimas -mj12bot mnogosearch moget -mojeekbot -momspider motor -msiecrawler -msnbot +^Mozilla$ +^Mozilla.4\.0$ +^Mozilla\/4\.0\+\(compatible;\)$ +^Mozilla\/4\.0\+\(compatible;\+ICS\)$ +^Mozilla\/4\.5\+\[en]\+\(Win98;\+I\)$ +^Mozilla.5\.0$ +^Mozilla\/5.0\+\(compatible;\+MSIE\+6\.0;\+Windows\+NT\+5\.0\)$ +^Mozilla\/5\.0\+like\+Gecko$ +^Mozilla\/5.0(\s|\+)Gecko\/20100115(\s|\+)Firefox\/3.6$ +^MSIE +MuscatFerre myweb nagios +^NetAnts\/\d netcraft netluchs +newspaper\/\d ng\/2\. +^Ning\/\d no_user_agent nomad nutch +^oaDOI$ ocelli +Offline(\s|\+)Navigator +OgScrper +okhttp onetszukaj +^Opera\/4$ +OurBrowser +panscient +parsijoo +^Pattern\/\d +Pcore-HTTP +pear\.php\.net perman +PHP\/ +pidcheck pioneer playmusic\.com playstarmusic\.com +^Postgenomic(\s|\+)v2 powermarks -psbot +proximic +PycURL python -qihoobot +Qwantify rambler -redalert|robozilla -robot -robots +ReactorNetty\/\d +Readpaper +redalert +Riddler +robozilla rss scan4mail scientificcommons scirus scooter -seekbot -seznambot +Scrapy\/\d +ScoutJet +^scrutiny\/\d +SearchBloxIntra shoutcast +Site24x7 +SkypeUriPreview slurp sogou speedy -spider -spiderman -spiderview +sqlmap +SrceDAMP +Strider +summify sunrise -superbot -surveybot +Sysomos +T\-H\-U\-N\-D\-E\-R\-S\-T\-O\-N\-E tailrank -technoratibot +Teleport(\s|\+)Pro +Teoma +The\+Knowledge\+AI titan -turnitinbot +^Traackr\.com$ +Trello +Trove +Turnitin twiceler +Typhoeus ucsd ultraseek +^undefined$ +^unknown$ +Unpaywall +URL2File urlaliasbuilder urllib -virus[_+]detector +^user.?agent$ +^User-Agent +validator +virus.detector voila -w3c\-checklink +^voltron$ +voyager\/ +w3af\.org +Wanadoo +Web(\s|\+)Downloader +WebCloner webcollage +WebCopier +Webinator weblayers +Webmetrics webmirror +webmon +weborama-fetcher webreaper +WebStripper +WebZIP +Wget +WhatsApp wordpress worm +www\.gnip\.com +WWW-Mechanize xenu y!j yacy yahoo -yahoo\-mmcrawler -yahoofeedseeker -yahooseeker yandex -yodaobot -zealbot +Yeti\/\d zeus zyborg -^$ -RePEc.link.checker -HttpComponents\/1.1 -RPT\-HTTPClient\/0.3-3E -URL2File\/1.981208 -parsijoo -validator -WWW\-Mechanize -^IDA$ -MarcEdit.5.2.Web.Client +7siters From 930d1c8f53a88e4225d98b3e61546091bf28171e Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sat, 4 Sep 2021 22:02:33 +0300 Subject: [PATCH 0198/1254] DS-4588: fix case-insensitive spiderdetector test We should not use the string "FirefOx" to test the validity of our spider user agent patterns because it is not a valid user agent in the first place. The Mozilla Firefox browser uses patterns such as this: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0 The test currently matches the following regular expression: ^firefox$ In this case, if a client *did* connect with a single-word string such as "Firefox" or "firefox" it would definitely be a non-human user and we should not log a statistics hit. If the goal of the test is to check *valid* human user agents aga- inst case-insensitive regular expression matching, then we should use the following: mozilla/5.0 (x11; linux x86_64; rv:91.0) gecko/20100101 firefox/91.0 Ported from #3336 to main for DSpace 7.x. --- .../dspace/statistics/util/SpiderDetectorServiceImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorServiceImplTest.java index 039fe31f11..24f8c0f124 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/util/SpiderDetectorServiceImplTest.java @@ -70,7 +70,7 @@ public class SpiderDetectorServiceImplTest extends AbstractDSpaceTest { req.setAgent("msnboT Is WaTching you"); assertTrue("'msnbot' didn't match pattern", spiderDetectorService.isSpider(req)); - req.setAgent("FirefOx"); + req.setAgent("mozilla/5.0 (x11; linux x86_64; rv:91.0) gecko/20100101 firefox/91.0"); assertFalse("'Firefox' matched a pattern", spiderDetectorService.isSpider(req)); // Test IP patterns From 4e8c8769dfe7cc1f650cee1c31b6b1ac6fe473af Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 4 Sep 2021 16:12:09 -0700 Subject: [PATCH 0199/1254] Service boundary and model updates. --- .../iiif/model/generator/CanvasGenerator.java | 66 +++++++++++--- .../generator/ExternalLinksGenerator.java | 43 +++++++-- .../generator/ImageContentGenerator.java | 47 +++------- .../generator/ImageServiceGenerator.java | 32 ++----- .../model/generator/ManifestGenerator.java | 23 +++-- .../iiif/model/generator/RangeGenerator.java | 5 -- .../iiif/service/AbstractResourceService.java | 55 ------------ .../iiif/service/AnnotationListService.java | 15 ++-- .../iiif/service/CanvasLookupService.java | 14 ++- .../app/rest/iiif/service/CanvasService.java | 61 ++++++++----- .../iiif/service/ImageContentService.java | 53 +++++++++++ .../rest/iiif/service/ManifestService.java | 88 +++++++------------ .../app/rest/iiif/service/RangeService.java | 67 ++++++++++++++ .../app/rest/iiif/service/RelatedService.java | 37 ++++++++ .../app/rest/iiif/service/SeeAlsoService.java | 41 +++++++++ .../rest/iiif/service/SequenceService.java | 34 ++++--- .../app/rest/iiif/service/util/IIIFUtils.java | 6 +- .../iiif/service/util/ImageProfileUtil.java | 3 - 18 files changed, 423 insertions(+), 267 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index 5de0a05b75..90af2ca0a7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -7,8 +7,10 @@ */ package org.dspace.app.rest.iiif.model.generator; +import java.util.ArrayList; +import java.util.List; + import de.digitalcollections.iiif.model.ImageContent; -import de.digitalcollections.iiif.model.PropertyValue; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import org.springframework.context.annotation.Scope; @@ -24,50 +26,61 @@ import org.springframework.stereotype.Component; @Scope("prototype") public class CanvasGenerator implements IIIFResource { - Canvas canvas; + private String identifier; + private String label; + private Integer height; + private Integer width; + private List images = new ArrayList(); + private ImageContent thumbnail; - public CanvasGenerator(String identifier) { - this.canvas = new Canvas(identifier); + public CanvasGenerator setIdentifier(String identifier) { + this.identifier = identifier; + return this; } /** * Every canvas must have a label to display. * @param label */ - public void setLabel(String label) { - canvas.setLabel(new PropertyValue(label)); + public CanvasGenerator setLabel(String label) { + this.label = label; + return this; } /** * Every canvas must have an integer height. * @param height */ - public void setHeight(int height) { - canvas.setHeight(height); + public CanvasGenerator setHeight(int height) { + this.height = height; + return this; } /** * Every canvas must have an integer width. * @param width */ - public void setWidth(int width) { - canvas.setWidth(width); + public CanvasGenerator setWidth(int width) { + this.width = width; + return this; } /** * Add to ImageContent resources that will be assigned to the canvas. * @param imageContent */ - public void addImage(Resource imageContent) { - canvas.addImage((ImageContent) imageContent); + public CanvasGenerator addImage(Resource imageContent) { + images.add((ImageContent) imageContent); + return this; } /** * The Thumbnail resource that will be assigned to the canvas. * @param thumbnail */ - public void addThumbnail(ImageContentGenerator thumbnail) { - canvas.addThumbnail((ImageContent) thumbnail.getResource()); + public CanvasGenerator addThumbnail(Resource thumbnail) { + this.thumbnail = (ImageContent) thumbnail; + return this; } /** @@ -76,6 +89,31 @@ public class CanvasGenerator implements IIIFResource { */ @Override public Resource getResource() { + /** + * The Canvas resource typically includes image content. + */ + Canvas canvas; + if (identifier == null) { + throw new RuntimeException("The Canvas resource requires an identifier."); + } + if (label != null) { + canvas = new Canvas(identifier, label); + } else { + canvas = new Canvas(identifier); + } + if (images.size() > 0) { + if (height == null || width == null) { + throw new RuntimeException("The Canvas resource requires both height and width dimensions."); + } + canvas.setWidth(width); + canvas.setHeight(height); + for (ImageContent res : images) { + canvas.addImage(res); + } + if (thumbnail != null) { + canvas.addThumbnail(thumbnail); + } + } return canvas; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java index 410a4553e2..8d77447758 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.OtherContent; +import de.digitalcollections.iiif.model.PropertyValue; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -25,38 +26,62 @@ import org.springframework.stereotype.Component; @Scope("prototype") public class ExternalLinksGenerator implements IIIFResource { - private OtherContent otherContent; + private String identifier; + private String format; + private String label; + private String type; - public ExternalLinksGenerator(String identifier) { - otherContent = new OtherContent(identifier); + /** + * Sets the mandatory identifier. + * @param identifier + */ + public ExternalLinksGenerator setIdentifier(String identifier) { + this.identifier = identifier; + return this; } /** * Sets the optional format. * @param format */ - public void setFormat(String format) { - otherContent.setFormat(format); + public ExternalLinksGenerator setFormat(String format) { + this.format = format; + return this; } /** * Sets the optional label. * @param label */ - public void setLabel(String label) { - otherContent.setLabel(new PropertyValueGenerator().getPropertyValue(label).getValue()); + public ExternalLinksGenerator setLabel(String label) { + this.label = label; + return this; } /** * Sets the optional type. * @param type */ - public void setType(String type) { - otherContent.setType(type); + public ExternalLinksGenerator setType(String type) { + this.type = type; + return this; } @Override public Resource getResource() { + OtherContent otherContent; + if (format != null) { + otherContent = new OtherContent(identifier, format); + } else { + otherContent = new OtherContent(identifier); + } + if (label != null) { + otherContent.setLabel(new PropertyValue(label)); + } + if (type != null) { + otherContent.setType(type); + } + return otherContent; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java index 64ef8033d5..e0224edb38 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java @@ -7,70 +7,45 @@ */ package org.dspace.app.rest.iiif.model.generator; -import java.util.ArrayList; -import java.util.List; - import de.digitalcollections.iiif.model.ImageContent; -import de.digitalcollections.iiif.model.Service; -import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; +import de.digitalcollections.iiif.model.sharedcanvas.Resource;; /** - * Facade for the domain model's ImageContent. + * POJO for the domain model's ImageContent. * * Presentation API version 2.1.1: The ImageContent entity is contained in the "resource" * field of annotations with motivation "sc:painting". Image resources, and only image resources, * are included in the images property of the canvas. This changes in API version 3.0. */ -@Component -@RequestScope public class ImageContentGenerator implements IIIFResource { - private String identifier; - private String mimetype; private org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator imageService; + private final ImageContent imageContent; - /** - * Sets the mandatory identifier for image content. - * @param identifier - */ - public void setIdentifier(String identifier) { - this.identifier = identifier; + public ImageContentGenerator(String identifier) { + imageContent = new ImageContent(identifier); } /** * Sets the optional mimetype. * @param mimetype */ - public void setFormat(String mimetype) { - this.mimetype = mimetype; + public ImageContentGenerator setFormat(String mimetype) { + imageContent.setFormat(mimetype); + return this; } /** * Sets the image service that the client will use to retrieve images. * @param imageService */ - public void addService(org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator imageService) { - this.imageService = imageService; + public ImageContentGenerator addService(ImageServiceGenerator imageService) { + this.imageContent.addService(imageService.getService()); + return this; } @Override public Resource getResource() { - if (identifier == null) { - throw new RuntimeException("The ImageContent resource requires an identifier."); - } - ImageContent imageContent = new ImageContent(identifier); - if (mimetype != null) { - imageContent.setFormat(mimetype); - } - // Supporting a single service for each image resource. - List services = new ArrayList<>(); - if (imageService != null) { - services.add(imageService.getService()); - } - imageContent.setServices(services); - return imageContent; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java index 7eaaa8cae3..8142e0a502 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java @@ -7,50 +7,32 @@ */ package org.dspace.app.rest.iiif.model.generator; -import java.util.ArrayList; - -import de.digitalcollections.iiif.model.Profile; import de.digitalcollections.iiif.model.Service; import de.digitalcollections.iiif.model.image.ImageService; -import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; /** - * Facade for API version 2.1.1 image service property. Added to + * POJO facade for API version 2.1.1 image service property. Added to * each image resource. */ -@Component -@RequestScope public class ImageServiceGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFService { - private String identifier; - private Profile profile; + private ImageService imageService; - /** - * Sets the mandatory identifier for the image service. - * @param identifier - */ - public void setIdentifier(String identifier) { - this.identifier = identifier; + public ImageServiceGenerator(String identifier) { + imageService = new ImageService(identifier); } /** * Sets the IIIF image profile. * @param profile */ - public void setProfile(org.dspace.app.rest.iiif.model.generator.ProfileGenerator profile) { - this.profile = profile.getValue(); - + public ImageServiceGenerator setProfile(org.dspace.app.rest.iiif.model.generator.ProfileGenerator profile) { + imageService.addProfile(profile.getValue()); + return this; } @Override public Service getService() { - ImageService imageService = new ImageService(identifier); - if (profile != null) { - ArrayList profiles = new ArrayList<>(); - profiles.add(profile); - imageService.setProfiles(profiles); - } return imageService; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 2efbeb99e0..b62048c018 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -36,8 +36,6 @@ import org.springframework.web.context.annotation.RequestScope; @RequestScope public class ManifestGenerator implements IIIFResource { - //private final Manifest manifest; - private String identifier; private String label; private PropertyValue description; @@ -50,8 +48,7 @@ public class ManifestGenerator implements IIIFResource { private ContentSearchService searchService; private final List license = new ArrayList<>(); private final List metadata = new ArrayList<>(); - private List ranges = new ArrayList<>(); - + private List ranges = new ArrayList<>(); @Autowired MetadataEntryGenerator metadataEntryGenerator; @@ -102,7 +99,7 @@ public class ManifestGenerator implements IIIFResource { * Add otional seeAlso element to Manifest. * @param seeAlso */ - public void addSeeAlso(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator seeAlso) { + public void addSeeAlso(ExternalLinksGenerator seeAlso) { this.seeAlso = (OtherContent) seeAlso.getResource(); } @@ -118,7 +115,7 @@ public class ManifestGenerator implements IIIFResource { * Add optional related element to Manifest. * @param related */ - public void addRelated(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator related) { + public void addRelated(ExternalLinksGenerator related) { this.related = (OtherContent) related.getResource(); } @@ -126,7 +123,7 @@ public class ManifestGenerator implements IIIFResource { * Adds optional search service to Manifest. * @param searchService */ - public void addService(org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator searchService) { + public void addService(ContentSearchGenerator searchService) { this.searchService = (ContentSearchService) searchService.getService(); } @@ -160,10 +157,10 @@ public class ManifestGenerator implements IIIFResource { /** * Adds optional Range to the manifest's structures element. - * @param range + * @param rangeGenerator */ - public void addRange(RangeGenerator range) { - ranges.add((Range) range.getResource()); + public void setRange(List rangeGenerator) { + ranges = rangeGenerator; } @Override @@ -186,8 +183,10 @@ public class ManifestGenerator implements IIIFResource { if (sequence != null) { manifest.addSequence(sequence); } - if (ranges.size() > 0) { - manifest.setRanges(ranges); + if (ranges != null && ranges.size() > 0) { + for (RangeGenerator range : ranges) { + manifest.addRange((Range) range.getResource()); + } } if (metadata.size() > 0) { for (MetadataEntry meta : metadata) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java index 7e9dd745e7..58018c948d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java @@ -63,11 +63,6 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. for (Canvas canvas : canvasList) { range.addCanvas(canvas); } - // Reset properties after each use - identifier = null; - canvasList.clear(); - label = null; - return range; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index ec979bf8cf..59e9a20be3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -9,9 +9,6 @@ package org.dspace.app.rest.iiif.service; import java.util.UUID; -import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; -import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator; -import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.app.rest.iiif.service.util.ImageProfileUtil; import org.dspace.app.rest.iiif.service.util.ThumbProfileUtil; @@ -55,11 +52,6 @@ public abstract class AbstractResourceService { @Autowired ImageProfileUtil imageUtil; - @Autowired - ImageContentGenerator imageContent; - - @Autowired - ImageServiceGenerator imageService; /** * Set constants using DSpace configuration definitions. @@ -84,52 +76,5 @@ public abstract class AbstractResourceService { return IIIF_ENDPOINT + uuid + "/manifest"; } - /** - * A small image that depicts or pictorially represents the resource that - * the property is attached to, such as the title page, a significant image - * or rendering of a canvas with multiple content resources associated with it. - * It is recommended that a IIIF Image API service be available for this image for - * manipulations such as resizing. - * - * This method returns a thumbnail annotation that includes the IIIF image service. - * - * @param uuid the bitstream id - * @return thumbnail Annotation - */ - protected ImageContentGenerator getThumbnailAnnotation(UUID uuid, String mimetype) throws - RuntimeException { - return getImageContent(uuid, mimetype, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); - } - - /** - * Association of images with their respective canvases is done via annotations. The Open Annotation model - * allows any resource to be associated with any other resource, or parts thereof, and it is reused for - * both commentary and painting resources on the canvas. - * @param uuid bitstream uuid - * @param mimetype bitstream mimetype - * @param profile the service profile - * @param path the path component of the identifier - * @return - */ - protected ImageContentGenerator getImageContent(UUID uuid, String mimetype, ProfileGenerator profile, String path) { - imageContent.setFormat(mimetype); - imageContent.setIdentifier(IMAGE_SERVICE + uuid + path); - imageContent.addService(getImageService(profile, uuid.toString())); - return imageContent; - } - - /** - * A link to a service that makes more functionality available for the resource, - * such as from an image to the base URI of an associated IIIF Image API service. - * - * @param profile service profile - * @param uuid id of the image bitstream - * @return object representing the Image Service - */ - private ImageServiceGenerator getImageService(ProfileGenerator profile, String uuid) { - imageService.setIdentifier(IMAGE_SERVICE + uuid); - imageService.setProfile(profile); - return imageService; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java index 880ff00dbf..4b35249dca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java @@ -47,11 +47,14 @@ public class AnnotationListService extends AbstractResourceService { @Autowired BitstreamFormatService bitstreamFormatService; + @Autowired + ExternalLinksGenerator externalLinksGenerator; + @Autowired AnnotationListGenerator annotationList; - public AnnotationListService(ApplicationContext applicationContext, ConfigurationService configurationService) { + public AnnotationListService(ApplicationContext applicationContext, ConfigurationService configurationService) { setConfiguration(configurationService); this.applicationContext = applicationContext; } @@ -112,10 +115,10 @@ public class AnnotationListService extends AbstractResourceService { + "/" + bitstream.getID() + "/content"; - ExternalLinksGenerator otherContentGenerator = applicationContext - .getBean(ExternalLinksGenerator.class, identifier); - otherContentGenerator.setFormat(mimetype); - otherContentGenerator.setLabel(bitstream.getName()); - return otherContentGenerator; + + return externalLinksGenerator + .setIdentifier(identifier) + .setFormat(mimetype) + .setLabel(bitstream.getName()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java index eff51e86f5..c22ea91627 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -46,16 +46,12 @@ public class CanvasLookupService extends AbstractResourceService { } Info info = utils.validateInfoForSingleCanvas(utils.getInfo(context, item, IIIF_BUNDLE), canvasPosition); - UUID bitstreamID = bitstream.getID(); + UUID bitstreamId = bitstream.getID(); String mimeType = utils.getBitstreamMimeType(bitstream, context); - CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, canvasPosition); - if (mimeType.contains("image/")) { - canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); - canvas.addImage(getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH) - .getResource()); - getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH).getResource(); - } - return utils.asJson(canvas.getResource()); + CanvasGenerator canvasGenerator = + canvasService.getCanvas(item.getID().toString(), bitstreamId, mimeType, info, canvasPosition); + + return utils.asJson(canvasGenerator.getResource()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index 77fde8ca9e..f5582d1c93 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -7,11 +7,14 @@ */ package org.dspace.app.rest.iiif.service; +import java.util.UUID; + import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.services.ConfigurationService; -import org.springframework.context.ApplicationContext; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -24,15 +27,19 @@ public class CanvasService extends AbstractResourceService { // Default canvas dimensions. protected static final Integer DEFAULT_CANVAS_WIDTH = 1200; protected static final Integer DEFAULT_CANVAS_HEIGHT = 1600; - ApplicationContext applicationContext; + + @Autowired + CanvasGenerator canvasGenerator; + + @Autowired + ImageContentService imageContentService; /** * Constructor. * @param configurationService the DSpace configuration service. */ - public CanvasService(ApplicationContext applicationContext, ConfigurationService configurationService) { + public CanvasService(ConfigurationService configurationService) { setConfiguration(configurationService); - this.applicationContext = applicationContext; } /** @@ -40,23 +47,27 @@ public class CanvasService extends AbstractResourceService { * Info object they are used. If canvas parameters are unavailable, default values * are used instead. * - * @param id manifest id + * Note that info.json is going to be replaced with metadata in the bitstream DSO. + * + * @param manifestId manifest id + * @param bitstreamId uuid of the bitstream + * @param mimeType the mimetype of the bitstream * @param info parameters for this canvas * @param count the canvas position in the sequence. * @return canvas object */ - protected CanvasGenerator getCanvas(String id, Info info, int count) { - CanvasGenerator canvas = - applicationContext.getBean(CanvasGenerator.class, IIIF_ENDPOINT + id + "/canvas/c" + count); - // canvas.setIdentifier(IIIF_ENDPOINT + id + "/canvas/c" + count); + protected CanvasGenerator getCanvas(String manifestId, UUID bitstreamId, String mimeType, Info info, int count) { int pagePosition = count + 1; + + // Defaults settings. Used if no info.json is provided. String label = "Page " + pagePosition; - // Defaults settings. int canvasWidth = DEFAULT_CANVAS_WIDTH; int canvasHeight = DEFAULT_CANVAS_HEIGHT; + // Override with settings from info.json, if available. if (info != null && info.getGlobalDefaults() != null && info.getCanvases() != null) { - // Use global settings if activated. + // The info.json file can request global defaults for canvas + // height, width and labels. Use global settings if activated in info.json. if (info.getGlobalDefaults().isActivated()) { // Create unique label by appending position to the default label. label = info.getGlobalDefaults().getLabel() + " " + pagePosition; @@ -64,7 +75,8 @@ public class CanvasService extends AbstractResourceService { canvasHeight = info.getGlobalDefaults().getHeight(); } else if (info.getCanvases().get(count) != null) { if (info.getCanvases().get(count).getLabel().length() > 0) { - // Individually defined canvas labels assumed unique, and are not incremented. + // Labels assumed unique and are not incremented + // when info.json provides individual canvas metadata. label = info.getCanvases().get(count).getLabel(); } canvasWidth = info.getCanvases().get(count).getWidth(); @@ -73,23 +85,30 @@ public class CanvasService extends AbstractResourceService { } else { log.info("Correctly formatted info.json was not found for item. Using application defaults."); } - canvas.setHeight(canvasHeight); - canvas.setWidth(canvasWidth); - canvas.setLabel(label); - return canvas; + + ImageContentGenerator image = imageContentService + .getImageContent(bitstreamId, mimeType, imageUtil.getImageProfile(), IMAGE_PATH); + + ImageContentGenerator thumb = imageContentService + .getImageContent(bitstreamId, mimeType, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); + + return canvasGenerator.setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) + .addImage(image.getResource()) + .addThumbnail(thumb.getResource()) + .setHeight(canvasHeight) + .setWidth(canvasWidth) + .setLabel(label); } + /** - * Ranges expect the Canvas object to have only an identifier. This method assures that the - * injected canvas facade is empty before setting the identifier. + * Ranges expect the Canvas object to have only an identifier. * @param identifier the DSpace item identifier * @param startCanvas the position of the canvas in list * @return */ protected CanvasGenerator getRangeCanvasReference(String identifier, String startCanvas) { - CanvasGenerator canvas = - applicationContext.getBean(CanvasGenerator.class, IIIF_ENDPOINT + identifier + startCanvas); - return canvas; + return canvasGenerator.setIdentifier(IIIF_ENDPOINT + identifier + startCanvas); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java new file mode 100644 index 0000000000..cd12a0316a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java @@ -0,0 +1,53 @@ +package org.dspace.app.rest.iiif.service; + +import java.util.UUID; + +import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator; +import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.dspace.services.ConfigurationService; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +@RequestScope +public class ImageContentService extends AbstractResourceService { + + + public ImageContentService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Association of images with their respective canvases is done via annotations. The Open Annotation model + * allows any resource to be associated with any other resource, or parts thereof, and it is reused for + * both commentary and painting resources on the canvas. + * @param uuid bitstream uuid + * @param mimetype bitstream mimetype + * @param profile the service profile + * @param path the path component of the identifier + * @return + */ + protected ImageContentGenerator getImageContent(UUID uuid, String mimetype, ProfileGenerator profile, String path) { + return new ImageContentGenerator(IMAGE_SERVICE + uuid + path) + .setFormat(mimetype) + .addService(getImageService(profile, uuid.toString())); + } + + protected ImageContentGenerator getImageContent(String identifier) { + return new ImageContentGenerator(identifier); + } + + /** + * A link to a service that makes more functionality available for the resource, + * like the Image API service. + * + * @param profile service profile + * @param uuid id of the image bitstream + * @return object representing the Image Service + */ + private ImageServiceGenerator getImageService(ProfileGenerator profile, String uuid) { + return new ImageServiceGenerator(IMAGE_SERVICE + uuid).setProfile(profile); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 119846434b..c13d0fee8f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -10,11 +10,9 @@ package org.dspace.app.rest.iiif.service; import java.util.List; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; -import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; -import org.dspace.app.rest.iiif.model.generator.RangeGenerator; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.app.rest.iiif.model.info.Range; import org.dspace.app.rest.iiif.service.util.IIIFUtils; @@ -26,7 +24,6 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @@ -39,30 +36,33 @@ public class ManifestService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ManifestService.class); - // TODO i18n - private static final String RELATED_ITEM_LABEL = "DSpace item view"; - private static final String SEE_ALSO_LABEL = "More descriptions of this resource"; - - private final ApplicationContext applicationContext; - @Autowired protected ItemService itemService; @Autowired CanvasService canvasService; + @Autowired + RangeService rangeService; + @Autowired SequenceService sequenceService; @Autowired - RangeGenerator rangeGenerator; + RelatedService relatedService; @Autowired - ContentSearchGenerator contentSearchGenerator; + SeeAlsoService seeAlsoService; + + @Autowired + ImageContentService imageContentService; @Autowired IIIFUtils utils; + @Autowired + ContentSearchGenerator contentSearchGenerator; + @Autowired ManifestGenerator manifestGenerator; @@ -71,9 +71,8 @@ public class ManifestService extends AbstractResourceService { * Constructor. * @param configurationService the DSpace configuration service. */ - public ManifestService(ApplicationContext applicationContext, ConfigurationService configurationService) { + public ManifestService(ConfigurationService configurationService) { setConfiguration(configurationService); - this.applicationContext = applicationContext; } /** @@ -100,6 +99,7 @@ public class ManifestService extends AbstractResourceService { // images in the ORIGINAL bundle will be used. List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); List bitstreams = utils.getBitstreams(bundles); + List thumbnailBundle = utils.getBundle(item, "THUMBNAIL"); Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); manifestGenerator.setIdentifier(getManifestId(item.getID())); manifestGenerator.setLabel(item.getName()); @@ -108,7 +108,7 @@ public class ManifestService extends AbstractResourceService { addSearchService(item); addMetadata(item.getMetadata()); addViewingHint(bitstreams.size()); - addThumbnail(bitstreams, context); + addThumbnail(thumbnailBundle, context); addSequence(item, bitstreams, context, info); addRanges(info, item.getID().toString()); addSeeAlso(item); @@ -161,12 +161,7 @@ public class ManifestService extends AbstractResourceService { * @param item the DSpace Item */ private void addRelated(Item item) { - String url = CLIENT_URL + "/items/" + item.getID(); - ExternalLinksGenerator otherContentGenerator = applicationContext - .getBean(ExternalLinksGenerator.class, url); - otherContentGenerator.setFormat("text/html"); - otherContentGenerator.setLabel(RELATED_ITEM_LABEL); - manifestGenerator.addRelated(otherContentGenerator); + manifestGenerator.addRelated(relatedService.getRelated(item)); } /** @@ -193,14 +188,9 @@ public class ManifestService extends AbstractResourceService { */ private void addSeeAlso(Item item) { List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); - if (bundles.size() == 0) { - return; + if (bundles.size() > 0) { + manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item, bundles)); } - ExternalLinksGenerator otherContentGenerator = applicationContext - .getBean(ExternalLinksGenerator.class, IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso"); - otherContentGenerator.setType(AnnotationGenerator.TYPE); - otherContentGenerator.setLabel(SEE_ALSO_LABEL); - manifestGenerator.addSeeAlso(otherContentGenerator); } /** @@ -228,49 +218,35 @@ public class ManifestService extends AbstractResourceService { private void addRanges(Info info, String identifier) { List rangesFromConfig = utils.getRangesFromInfoObject(info); if (rangesFromConfig != null) { - for (int pos = 0; pos < rangesFromConfig.size(); pos++) { - setRange(identifier, rangesFromConfig.get(pos), pos); - manifestGenerator.addRange(rangeGenerator); - } + manifestGenerator.setRange(rangeService.getRanges(info, identifier)); } } /** - * Sets properties on the Range. - * @param identifier DSpace item id - * @param range range from info.json configuration - * @param pos list position of the range + * Adds thumbnail to the manifest. Uses the thumbnail in DSpace thumbnail bundle. + * @param bundles contain the THUMBNAIL bundle + * @param context DSpac context */ - private void setRange(String identifier, Range range, int pos) { - String id = IIIF_ENDPOINT + identifier + "/r" + pos; - String label = range.getLabel(); - rangeGenerator.setIdentifier(id); - rangeGenerator.setLabel(label); - String startCanvas = utils.getCanvasId(range.getStart()); - rangeGenerator.addCanvas(canvasService.getRangeCanvasReference(identifier, startCanvas)); - } - - /** - * Adds thumbnail to the manifest - * @param bitstreams - * @param context - */ - private void addThumbnail(List bitstreams, Context context) { - if (bitstreams.size() > 0) { + private void addThumbnail(List bundles, Context context) { + List bitstreams = utils.getBitstreams(bundles); + if (bitstreams != null && bitstreams.size() > 0) { String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context); if (utils.checkImageMimeType(mimeType)) { - manifestGenerator.addThumbnail(getThumbnailAnnotation(bitstreams.get(0).getID(), mimeType)); + ImageContentGenerator image = imageContentService + .getImageContent(bitstreams.get(0).getID(), mimeType, + thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); + manifestGenerator.addThumbnail(image); } } } /** - * If the logo is defined in DSpace configura + * If the logo is defined in DSpace configuration add to manifest. */ private void setLogoContainer() { if (IIIF_LOGO_IMAGE != null) { - imageContent.setIdentifier(IIIF_LOGO_IMAGE); - manifestGenerator.addLogo(imageContent); + ImageContentGenerator logo = new ImageContentGenerator(IIIF_LOGO_IMAGE); + manifestGenerator.addLogo(logo); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java new file mode 100644 index 0000000000..931e3a0c6d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.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.iiif.service; + +import java.util.ArrayList; +import java.util.List; + +import org.dspace.app.rest.iiif.model.generator.RangeGenerator; +import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.app.rest.iiif.model.info.Range; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +@RequestScope +public class RangeService extends AbstractResourceService { + + @Autowired + RangeGenerator rangeGenerator; + + @Autowired + CanvasService canvasService; + + public RangeService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + /** + * Adds Ranges to manifest structures element. + * Ranges are defined in the info.json file. + * @param info + * @param identifier + */ + public List getRanges(Info info, String identifier) { + List ranges = new ArrayList<>(); + List rangesFromConfig = utils.getRangesFromInfoObject(info); + if (rangesFromConfig != null) { + for (int pos = 0; pos < rangesFromConfig.size(); pos++) { + ranges.add(getRange(identifier, rangesFromConfig.get(pos), pos)); + } + } + return ranges; + } + + /** + * Sets properties on the Range. + * @param identifier DSpace item id + * @param range range from info.json configuration + * @param pos list position of the range + */ + private RangeGenerator getRange(String identifier, Range range, int pos) { + String id = IIIF_ENDPOINT + identifier + "/r" + pos; + String label = range.getLabel(); + rangeGenerator.setIdentifier(id); + rangeGenerator.setLabel(label); + String startCanvas = utils.getCanvasId(range.getStart()); + rangeGenerator.addCanvas(canvasService.getRangeCanvasReference(identifier, startCanvas)); + return rangeGenerator; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java new file mode 100644 index 0000000000..ba06c4f720 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.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.iiif.service; + +import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +@RequestScope +public class RelatedService extends AbstractResourceService { + + private static final String RELATED_ITEM_LABEL = "DSpace item view"; + + public RelatedService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + @Autowired + ExternalLinksGenerator externalLinksGenerator; + + public ExternalLinksGenerator getRelated(Item item) { + String url = CLIENT_URL + "/items/" + item.getID(); + externalLinksGenerator.setIdentifier(url); + externalLinksGenerator.setFormat("text/html"); + externalLinksGenerator.setLabel(RELATED_ITEM_LABEL); + return externalLinksGenerator; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java new file mode 100644 index 0000000000..79a8bea0d0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.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.app.rest.iiif.service; + +import java.util.List; + +import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +@RequestScope +public class SeeAlsoService extends AbstractResourceService { + + private static final String SEE_ALSO_LABEL = "More descriptions of this resource"; + + public SeeAlsoService(ConfigurationService configurationService) { + setConfiguration(configurationService); + } + + @Autowired + ExternalLinksGenerator externalLinksGenerator; + + public ExternalLinksGenerator getSeeAlso(Item item, List bundles) { + return externalLinksGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso") + .setType(AnnotationGenerator.TYPE) + .setLabel(SEE_ALSO_LABEL); + + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java index 6b61ff98f5..a18a736283 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java @@ -11,10 +11,10 @@ import java.sql.SQLException; import java.util.List; import java.util.UUID; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; -import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -30,12 +30,17 @@ import org.springframework.stereotype.Component; @Scope("prototype") public class SequenceService extends AbstractResourceService { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SequenceService.class); + // TODO i18n private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; @Autowired CanvasItemsGenerator sequenceGenerator; + @Autowired + ExternalLinksGenerator externalLinksGenerator; + @Autowired CanvasService canvasService; @@ -73,16 +78,18 @@ public class SequenceService extends AbstractResourceService { * bundle. */ int counter = 0; + if (bitstreams == null || bitstreams.size() == 0) { + throw new RuntimeException("No bitstreams found for " + item.getID() + + ". Cannot add media content to the manifest."); + } for (Bitstream bitstream : bitstreams) { - UUID bitstreamID = bitstream.getID(); + UUID bitstreamId = bitstream.getID(); String mimeType = utils.getBitstreamMimeType(bitstream, context); if (utils.checkImageMimeType(mimeType)) { - CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, counter); - ImageContentGenerator imageGenerator = - getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH); - canvas.addImage(imageGenerator.getResource()); - canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType)); - sequenceGenerator.addCanvas(canvas); + String manifestId = item.getID().toString(); + CanvasGenerator canvasGenerator = + canvasService.getCanvas(manifestId, bitstreamId, mimeType, info, counter); + sequenceGenerator.addCanvas(canvasGenerator); counter++; } } @@ -121,11 +128,12 @@ public class SequenceService extends AbstractResourceService { // field, e.g. iiif.rendering if (mimeType != null && mimeType.contentEquals("application/pdf")) { String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; - ExternalLinksGenerator otherContentGenerator = applicationContext - .getBean(ExternalLinksGenerator.class, id); - otherContentGenerator.setLabel(PDF_DOWNLOAD_LABEL); - otherContentGenerator.setFormat(mimeType); - sequenceGenerator.addRendering(otherContentGenerator); + sequenceGenerator.addRendering( + externalLinksGenerator + .setIdentifier(id) + .setLabel(PDF_DOWNLOAD_LABEL) + .setFormat(mimeType) + ); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 73dc8556a1..03bfe20d48 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -88,10 +88,10 @@ public class IIIFUtils { * @return list of bitstreams */ public List getBitstreams(List bundles) { - if (bundles == null || bundles.size() == 0) { - throw new RuntimeException("Unable to retrieve DSpace bundle for manifest."); + if (bundles != null && bundles.size() > 0) { + return bundles.get(0).getBitstreams(); } - return bundles.get(0).getBitstreams(); + return null; } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java index 63fa43910e..c1aeb2555c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java @@ -19,9 +19,6 @@ public class ImageProfileUtil { /** * Utility method for obtaining the image service profile. - * Calling from this utility provides a unique instance of the - * autowired property. Necessary because a single canvas resource contains - * both thumbnail and images. * * @return image service profile */ From 618d72b4b9e4f25fbd3cb675db2c4d801c9fded1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 4 Sep 2021 17:10:00 -0700 Subject: [PATCH 0200/1254] Updated range and image services. --- .../iiif/model/generator/ImageContentGenerator.java | 4 ++-- .../rest/iiif/model/generator/RangeGenerator.java | 13 ++++++------- .../app/rest/iiif/service/ImageContentService.java | 7 +++++++ .../dspace/app/rest/iiif/service/RangeService.java | 4 +--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java index e0224edb38..48388af6ec 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java @@ -8,14 +8,14 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.ImageContent; -import de.digitalcollections.iiif.model.sharedcanvas.Resource;; +import de.digitalcollections.iiif.model.sharedcanvas.Resource; /** * POJO for the domain model's ImageContent. * * Presentation API version 2.1.1: The ImageContent entity is contained in the "resource" * field of annotations with motivation "sc:painting". Image resources, and only image resources, - * are included in the images property of the canvas. This changes in API version 3.0. + * are included in the image's property of the canvas. This changes in API version 3.0. */ public class ImageContentGenerator implements IIIFResource { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java index 58018c948d..0d2737d5a7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java @@ -13,8 +13,6 @@ import java.util.List; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; /** * In Presentation API version 2.1.1, adding a range to the manifest allows the client to display a structured @@ -25,8 +23,6 @@ import org.springframework.stereotype.Component; * This is used to populate the "structures" element of the Manifest. (The REST API service looks to the "info.json" * file for ranges.) */ -@Component -@Scope("prototype") public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { private String identifier; @@ -37,24 +33,27 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. * Sets mandatory range identifier. * @param identifier */ - public void setIdentifier(String identifier) { + public RangeGenerator setIdentifier(String identifier) { this.identifier = identifier; + return this; } /** * Sets mandatory range label. * @param label */ - public void setLabel(String label) { + public RangeGenerator setLabel(String label) { this.label = label; + return this; } /** * Adds canvas to Range canvas list. * @param canvas */ - public void addCanvas(CanvasGenerator canvas) { + public RangeGenerator addCanvas(CanvasGenerator canvas) { canvasList.add((Canvas) canvas.getResource()); + return this; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java index cd12a0316a..c7e365f690 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.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.iiif.service; import java.util.UUID; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java index 931e3a0c6d..76de7d7643 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java @@ -22,9 +22,6 @@ import org.springframework.web.context.annotation.RequestScope; @RequestScope public class RangeService extends AbstractResourceService { - @Autowired - RangeGenerator rangeGenerator; - @Autowired CanvasService canvasService; @@ -58,6 +55,7 @@ public class RangeService extends AbstractResourceService { private RangeGenerator getRange(String identifier, Range range, int pos) { String id = IIIF_ENDPOINT + identifier + "/r" + pos; String label = range.getLabel(); + RangeGenerator rangeGenerator = new RangeGenerator(); rangeGenerator.setIdentifier(id); rangeGenerator.setLabel(label); String startCanvas = utils.getCanvasId(range.getStart()); From 015f1f2ac950f31391e3d2eaffebe68dca1b80ca Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 4 Sep 2021 17:10:46 -0700 Subject: [PATCH 0201/1254] Fixed IT. --- .../dspace/app/rest/iiif/IIIFRestRepositoryIT.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java index d06573a258..c165c435d4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java @@ -94,6 +94,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { String bitstreamContent = "ThisIsSomeDummyText"; Bitstream bitstream1 = null; + Bitstream bitstream2 = null; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { bitstream1 = BitstreamBuilder .createBitstream(context, publicItem1, is, IIIFBundle) @@ -102,6 +103,14 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder + .createBitstream(context, publicItem1, is, "THUMBNAIL") + .withName("Bitstream2") + .withMimeType("image/jpeg") + .build(); + } + context.restoreAuthSystemState(); // Default canvas size and label. @@ -110,7 +119,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.service.profile", is("http://iiif.io/api/search/0/search"))) .andExpect(jsonPath("$.thumbnail.@id", Matchers.containsString("/iiif/2/" - + bitstream1.getID()))) + + bitstream2.getID()))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) From 39298006fb0b74183d16b3acfec6d6c848ab3e8c Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 4 Sep 2021 17:34:12 -0700 Subject: [PATCH 0202/1254] Reverted manifest thumbnail image. --- .../dspace/app/rest/iiif/service/ManifestService.java | 10 +++++----- .../org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index c13d0fee8f..18591c9a8f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -99,7 +99,7 @@ public class ManifestService extends AbstractResourceService { // images in the ORIGINAL bundle will be used. List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); List bitstreams = utils.getBitstreams(bundles); - List thumbnailBundle = utils.getBundle(item, "THUMBNAIL"); + // List thumbnailBundle = utils.getBundle(item, "THUMBNAIL"); Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); manifestGenerator.setIdentifier(getManifestId(item.getID())); manifestGenerator.setLabel(item.getName()); @@ -108,7 +108,7 @@ public class ManifestService extends AbstractResourceService { addSearchService(item); addMetadata(item.getMetadata()); addViewingHint(bitstreams.size()); - addThumbnail(thumbnailBundle, context); + addThumbnail(bundles, context); addSequence(item, bitstreams, context, info); addRanges(info, item.getID().toString()); addSeeAlso(item); @@ -223,9 +223,9 @@ public class ManifestService extends AbstractResourceService { } /** - * Adds thumbnail to the manifest. Uses the thumbnail in DSpace thumbnail bundle. - * @param bundles contain the THUMBNAIL bundle - * @param context DSpac context + * Adds thumbnail to the manifest. Uses first image in bundle. + * @param bundles image bundles + * @param context DSpace context */ private void addThumbnail(List bundles, Context context) { List bitstreams = utils.getBitstreams(bundles); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java index c165c435d4..d06eb8d266 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java @@ -105,7 +105,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { bitstream2 = BitstreamBuilder - .createBitstream(context, publicItem1, is, "THUMBNAIL") + .createBitstream(context, publicItem1, is, IIIFBundle) .withName("Bitstream2") .withMimeType("image/jpeg") .build(); @@ -119,7 +119,7 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.service.profile", is("http://iiif.io/api/search/0/search"))) .andExpect(jsonPath("$.thumbnail.@id", Matchers.containsString("/iiif/2/" - + bitstream2.getID()))) + + bitstream1.getID()))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) From 5b75e87c7de6709876e3de961300f4b3abe193d1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 4 Sep 2021 18:14:00 -0700 Subject: [PATCH 0203/1254] Fixed runaway sequence. --- .../app/rest/iiif/model/generator/CanvasGenerator.java | 4 ---- .../org/dspace/app/rest/iiif/service/CanvasService.java | 7 ++----- .../dspace/app/rest/iiif/service/ImageContentService.java | 4 ++-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index 90af2ca0a7..949efab1ac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -13,8 +13,6 @@ import java.util.List; import de.digitalcollections.iiif.model.ImageContent; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; /** * Presentation API version 2.1.1 Canvas model. @@ -22,8 +20,6 @@ import org.springframework.stereotype.Component; * Changes a Presentation API version 3.0 will likely require updates for * multiple media types, etc. */ -@Component -@Scope("prototype") public class CanvasGenerator implements IIIFResource { private String identifier; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index f5582d1c93..c43cdbcaa7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -28,9 +28,6 @@ public class CanvasService extends AbstractResourceService { protected static final Integer DEFAULT_CANVAS_WIDTH = 1200; protected static final Integer DEFAULT_CANVAS_HEIGHT = 1600; - @Autowired - CanvasGenerator canvasGenerator; - @Autowired ImageContentService imageContentService; @@ -92,7 +89,7 @@ public class CanvasService extends AbstractResourceService { ImageContentGenerator thumb = imageContentService .getImageContent(bitstreamId, mimeType, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); - return canvasGenerator.setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) + return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) .addImage(image.getResource()) .addThumbnail(thumb.getResource()) .setHeight(canvasHeight) @@ -108,7 +105,7 @@ public class CanvasService extends AbstractResourceService { * @return */ protected CanvasGenerator getRangeCanvasReference(String identifier, String startCanvas) { - return canvasGenerator.setIdentifier(IIIF_ENDPOINT + identifier + startCanvas); + return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + identifier + startCanvas); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java index c7e365f690..476ad27474 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java @@ -13,11 +13,11 @@ import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator; import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; import org.dspace.services.ConfigurationService; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; @Component -@RequestScope +@Scope("prototype") public class ImageContentService extends AbstractResourceService { From 813f81b3bcf5172fd968690e7b030aa34fa9d875 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 4 Sep 2021 21:03:06 -0700 Subject: [PATCH 0204/1254] Javadoc updates. --- .../model/generator/ManifestGenerator.java | 20 +++++++++---------- .../generator/MetadataEntryGenerator.java | 8 ++++++++ .../iiif/model/generator/RangeGenerator.java | 6 +++--- .../rest/iiif/service/ManifestService.java | 1 - 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index b62048c018..05b067732f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -89,7 +89,7 @@ public class ManifestGenerator implements IIIFResource { /** * Use to add single mandatory sequence to Manifest. In IIIF Presentation API 3.0 "sequence" * is replaced by "items" - * @param sequence + * @param sequence canvas list model (sequence) */ public void addSequence(CanvasItemsGenerator sequence) { this.sequence = (Sequence) sequence.getResource(); @@ -97,7 +97,7 @@ public class ManifestGenerator implements IIIFResource { /** * Add otional seeAlso element to Manifest. - * @param seeAlso + * @param seeAlso other content model */ public void addSeeAlso(ExternalLinksGenerator seeAlso) { this.seeAlso = (OtherContent) seeAlso.getResource(); @@ -105,7 +105,7 @@ public class ManifestGenerator implements IIIFResource { /** * Add optional thumbnail image to manifest. - * @param thumbnail + * @param thumbnail image content model */ public void addThumbnail(ImageContentGenerator thumbnail) { this.thumbnail = (ImageContent) thumbnail.getResource(); @@ -113,7 +113,7 @@ public class ManifestGenerator implements IIIFResource { /** * Add optional related element to Manifest. - * @param related + * @param related other content model */ public void addRelated(ExternalLinksGenerator related) { this.related = (OtherContent) related.getResource(); @@ -121,7 +121,7 @@ public class ManifestGenerator implements IIIFResource { /** * Adds optional search service to Manifest. - * @param searchService + * @param searchService search service model */ public void addService(ContentSearchGenerator searchService) { this.searchService = (ContentSearchService) searchService.getService(); @@ -129,8 +129,8 @@ public class ManifestGenerator implements IIIFResource { /** * Adds single metadata field to Manifest. - * @param field - * @param value + * @param field property field + * @param value property value */ public void addMetadata(String field, String value) { metadataEntryGenerator.setField(field); @@ -148,8 +148,8 @@ public class ManifestGenerator implements IIIFResource { /** * Adds optional description to Manifest. - * @param field - * @param value + * @param field property field + * @param value property value */ public void addDescription(String field, String value) { description = new PropertyValueGenerator().getPropertyValue(field, value).getValue(); @@ -157,7 +157,7 @@ public class ManifestGenerator implements IIIFResource { /** * Adds optional Range to the manifest's structures element. - * @param rangeGenerator + * @param rangeGenerator list of range models */ public void setRange(List rangeGenerator) { ranges = rangeGenerator; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java index 57dccadfa7..53d2987431 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java @@ -16,10 +16,18 @@ public class MetadataEntryGenerator implements IIIFValue { private String field; private String value; + /** + * Set metadata field name. + * @param field field name + */ public void setField(String field) { this.field = field; } + /** + * Set metadata value. + * @param value metadata value + */ public void setValue(String value) { this.value = value; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java index 0d2737d5a7..ff92f6a7a6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java @@ -31,7 +31,7 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. /** * Sets mandatory range identifier. - * @param identifier + * @param identifier range identifier */ public RangeGenerator setIdentifier(String identifier) { this.identifier = identifier; @@ -40,7 +40,7 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. /** * Sets mandatory range label. - * @param label + * @param label range label */ public RangeGenerator setLabel(String label) { this.label = label; @@ -49,7 +49,7 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. /** * Adds canvas to Range canvas list. - * @param canvas + * @param canvas list of canvas models */ public RangeGenerator addCanvas(CanvasGenerator canvas) { canvasList.add((Canvas) canvas.getResource()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 18591c9a8f..5600b89a94 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -99,7 +99,6 @@ public class ManifestService extends AbstractResourceService { // images in the ORIGINAL bundle will be used. List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); List bitstreams = utils.getBitstreams(bundles); - // List thumbnailBundle = utils.getBundle(item, "THUMBNAIL"); Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); manifestGenerator.setIdentifier(getManifestId(item.getID())); manifestGenerator.setLabel(item.getName()); From 7f22c91dbc17dfacb3915654cf21ce5d9692f4d7 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 5 Sep 2021 13:38:50 -0700 Subject: [PATCH 0205/1254] Minor fix in sequence and an implementation comment in the manifest service. --- .../org/dspace/app/rest/iiif/service/ManifestService.java | 5 +++++ .../org/dspace/app/rest/iiif/service/SequenceService.java | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 5600b89a94..5bd6b9ab8c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -121,6 +121,11 @@ public class ManifestService extends AbstractResourceService { * @return a sequence of canvases */ private void addSequence(Item item, List bitstreams, Context context, Info info) { + // After replacing the info object with DSO metadata we might + // update this method to iterate over the bitstreams list, passing + // the individual bitstream and position to revised methods in + // sequenceService and rangeService. But it's hard to try now without more + // work elsewhere. manifestGenerator.addSequence( sequenceService.getSequence(item, bitstreams, context, info)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java index a18a736283..08047124bf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java @@ -44,11 +44,9 @@ public class SequenceService extends AbstractResourceService { @Autowired CanvasService canvasService; - ApplicationContext applicationContext; - public SequenceService(ApplicationContext applicationContext, ConfigurationService configurationService) { + public SequenceService(ConfigurationService configurationService) { setConfiguration(configurationService); - this.applicationContext = applicationContext; } public CanvasItemsGenerator getSequence(Item item, List bitstreams, Context context, Info info) { From 05ce413ab5d5608b62fbe897de7604064182918c Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 5 Sep 2021 13:41:18 -0700 Subject: [PATCH 0206/1254] Removed unused import. --- .../java/org/dspace/app/rest/iiif/service/SequenceService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java index 08047124bf..ef0d190ac4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java @@ -22,7 +22,6 @@ import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; From d61539eead926c7839caad256247de26db5ef884 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 6 Sep 2021 10:38:48 -0400 Subject: [PATCH 0207/1254] Remove unused helper class. (#2129) --- .../dspace/builder/RequestItemHelperDAO.java | 40 ------------------- 1 file changed, 40 deletions(-) delete mode 100644 dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.java deleted file mode 100644 index d86b3d22b5..0000000000 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemHelperDAO.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.builder; - -import java.sql.SQLException; -import javax.persistence.Query; - -import org.dspace.app.requestitem.RequestItem; -import org.dspace.core.AbstractHibernateDAO; -import org.dspace.core.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Supply missing "delete" operation on RequestItem, to support testing. - * - * @author Mark H. Wood - */ -public class RequestItemHelperDAO extends AbstractHibernateDAO { - Logger LOG = LoggerFactory.getLogger(RequestItemHelperDAO.class); - - void delete(Context context, String token) - throws SQLException { - LOG.debug("delete request with token {}", token); - - Query delete = createQuery(context, "DELETE FROM " - + RequestItem.class.getSimpleName() - + " WHERE token = :token"); - delete.setParameter("token", token); - int howmany = delete.executeUpdate(); - - LOG.debug("Deleted {} requests", howmany); - } -} From 5fc0f595e67f7457e329d05c26f8199a0318602e Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 6 Sep 2021 10:40:05 -0400 Subject: [PATCH 0208/1254] Increase test coverage. (#2129) --- .../app/rest/RequestItemRepositoryIT.java | 119 ++++++++++++++++++ 1 file changed, 119 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 94e29592a6..5f2ca02c6c 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 @@ -15,6 +15,7 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.text.IsEmptyString.emptyOrNullString; import static org.junit.Assert.assertEquals; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -27,6 +28,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.Cookie; @@ -105,6 +107,20 @@ public class RequestItemRepositoryIT context.restoreAuthSystemState(); } + /** + * Test of findAll method. + * + * @throws Exception passed through. + */ + @Test + public void testFindAll() + throws Exception { + System.out.println("findAll"); + + getClient().perform(get(URI_ROOT)) + .andExpect(status().isMethodNotAllowed()); + } + /** * Test of findOne method, with an authenticated user. * @@ -154,6 +170,21 @@ public class RequestItemRepositoryIT RequestCopyMatcher.matchRequestCopy(request)))); } + /** + * Test of findOne with an unknown token. + * + * @throws Exception passed through. + */ + @Test + public void testFindOneNonexistent() + throws Exception { + System.out.println("findOne (nonexistent request)"); + + String uri = URI_ROOT + "/impossible"; + getClient().perform(get(uri)) + .andExpect(status().isNotFound()); + } + /** * Test of createAndReturn method, with an authenticated user. * @@ -260,6 +291,82 @@ public class RequestItemRepositoryIT } } + /** + * Test of createAndReturn method, with various errors. + * + * @throws java.sql.SQLException passed through. + * @throws org.dspace.authorize.AuthorizeException passed through. + * @throws java.io.IOException passed through. + */ + @Test + public void testCreateAndReturnBadRequest() + throws SQLException, AuthorizeException, IOException, Exception { + System.out.println("createAndReturn (authenticated)"); + + // Fake up a request in REST form. + RequestItemRest rir = new RequestItemRest(); + rir.setBitstreamId(bitstream.getID().toString()); + rir.setItemId(item.getID().toString()); + rir.setRequestEmail(RequestItemBuilder.REQ_EMAIL); + rir.setRequestMessage(RequestItemBuilder.REQ_MESSAGE); + rir.setRequestName(RequestItemBuilder.REQ_NAME); + rir.setAllfiles(false); + + // Try to create it, with various malformations. + ObjectMapper mapper = new ObjectMapper(); + String authToken = getAuthToken(admin.getEmail(), password); + AtomicReference requestTokenRef = new AtomicReference<>(); + + try { + // Test missing bitstream ID + rir.setBitstreamId(null); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + // Test unknown bitstream ID + rir.setBitstreamId(UUID.randomUUID().toString()); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + rir.setBitstreamId(bitstream.getID().toString()); + + // Test missing item ID + rir.setItemId(null); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + // Test unknown item ID + rir.setItemId(UUID.randomUUID().toString()); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + rir.setItemId(item.getID().toString()); + + // Test missing email + rir.setRequestEmail(null); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } finally { + // Clean up the created request. + RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); + } + } + /** * Verify that Spring Security's CSRF protection is working as we expect. * We must test this using a simple non-GET request, as CSRF Tokens are not @@ -312,6 +419,18 @@ public class RequestItemRepositoryIT .andExpect(status().isNoContent()); } + @Test + public void testDelete() + throws Exception { + System.out.println("delete"); + + RequestItem request = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .build(); + getClient().perform(delete(URI_ROOT + '/' + request.getToken())) + .andExpect(status().isMethodNotAllowed()); + } + /** * Verify that Spring Security's CORS settings are working as we expect. * From dc1fb643bd392fb70d603e1d94412a2d2ae755a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 6 Sep 2021 18:02:36 +0100 Subject: [PATCH 0209/1254] remove comment --- .../main/java/org/dspace/external/OpenAIRERestConnector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 954b6ac1e7..3b5d6b4c94 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -35,6 +35,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.logging.log4j.Logger; +import org.dspace.app.util.Util; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; @@ -104,7 +105,7 @@ public class OpenAIRERestConnector { HttpPost httpPost = new HttpPost(tokenServiceUrl); httpPost.addHeader("Accept", "application/json"); - httpPost.addHeader("User-Agent", "DSpace/7.X"); + httpPost.addHeader("User-Agent", "DSpace/" + Util.getSourceVersion()); httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); httpPost.setHeader(HttpHeaders.AUTHORIZATION, authHeader); @@ -201,7 +202,6 @@ public class OpenAIRERestConnector { // do not close this httpClient result = getResponse.getEntity().getContent(); } catch (MalformedURLException e1) { - // TODO Auto-generated catch block getGotError(e1, url + '/' + file); } catch (Exception e) { getGotError(e, url + '/' + file); From 0f60fe85ece3527cc5f97a754f5f33c0c0336d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 6 Sep 2021 18:07:18 +0100 Subject: [PATCH 0210/1254] support openaire4 types registries by default --- dspace/config/dspace.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index c5dbae0e64..22f6af39f4 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -817,6 +817,7 @@ registry.metadata.load = schema-organization-types.xml registry.metadata.load = schema-periodical-types.xml registry.metadata.load = schema-publicationIssue-types.xml registry.metadata.load = schema-publicationVolume-types.xml +registry.metadata.load = openaire4-types.xml registry.metadata.load = dspace-types.xml From 08857aecd2496c84233b262d30d9606c374066f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 6 Sep 2021 18:11:10 +0100 Subject: [PATCH 0211/1254] renaming properties and improve file documentation --- .../external/OpenAIRERestConnector.java | 6 ++--- dspace/config/modules/openaire-client.cfg | 25 ++++++++++++------- .../config/spring/api/external-openaire.xml | 8 +++--- 3 files changed, 23 insertions(+), 16 deletions(-) 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 3b5d6b4c94..2d39786264 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -60,7 +60,7 @@ public class OpenAIRERestConnector { /** * Boolean with token usage definition true if we want to use a token */ - boolean tokenUsage; + boolean tokenEnabled; /** * OpenAIRE Authorization and Authentication Token Service URL @@ -251,7 +251,7 @@ public class OpenAIRERestConnector { String queryString = path + ((path.indexOf("?") > 0) ? "&" : "?") + String.join("&", queryStringPagination); InputStream result = null; - if (tokenUsage) { + if (tokenEnabled) { try { if (accessToken == null) { accessToken = this.grabNewAccessToken(); @@ -332,7 +332,7 @@ public class OpenAIRERestConnector { */ @Autowired(required = false) public void setTokenUsage(boolean tokenUsage) { - this.tokenUsage = tokenUsage; + this.tokenEnabled = tokenUsage; } protected void getGotError(Exception e, String fullPath) { diff --git a/dspace/config/modules/openaire-client.cfg b/dspace/config/modules/openaire-client.cfg index 34cbd993f2..ded99d0120 100644 --- a/dspace/config/modules/openaire-client.cfg +++ b/dspace/config/modules/openaire-client.cfg @@ -3,26 +3,33 @@ #---------------------------------------------------------------# # These configs are used by the OPENAIRE rest client # #---------------------------------------------------------------# - -# In order to use the API without the imposed number of requests limitation +# +# In order to use the API without the imposed number of requests limitation (60 requests per hour) # you will need to use an accessToken. +# +# +# FOR A PRODUCTION ENVIRONMENT USAGE +# IT'S ADVISABLE TO REGISTER AN ACCOUNT AT OPENAIRE! +# (https://develop.openaire.eu/personalToken.html) +# +# # The accessToken it only has a validity of one hour # For more details about the token, please check: https://develop.openaire.eu/personalToken.html - +# # the current OpenAIRE Rest client implementation uses basic authentication # Described here: https://develop.openaire.eu/basic.html - +# # ---- Token usage required definitions ---- -# you can override this settings in your local.cfg file -openaire.aai.tokenUsage = false +# you can override this settings in your local.cfg file - can be true/false +openaire.token.enabled = false # URL of the OpenAIRE authentication and authorization service -openaire.aai.url = https://aai.openaire.eu/oidc/token +openaire.token.url = https://aai.openaire.eu/oidc/token # you will be required to register at OpenAIRE (https://services.openaire.eu/uoa-user-management/registeredServices) # and create your service in order to get the following data: -openaire.aai.clientId = CLIENT_ID_HERE -openaire.aai.clientSecret = CLIENT_SECRET_HERE +openaire.token.clientId = CLIENT_ID_HERE +openaire.token.clientSecret = CLIENT_SECRET_HERE # URL of OpenAIRE Rest API openaire.api.url = https://api.openaire.eu \ No newline at end of file diff --git a/dspace/config/spring/api/external-openaire.xml b/dspace/config/spring/api/external-openaire.xml index 719decd777..1bff3af7b8 100644 --- a/dspace/config/spring/api/external-openaire.xml +++ b/dspace/config/spring/api/external-openaire.xml @@ -5,10 +5,10 @@ - - - - + + + + From 5bb9fce3b6e0b49fa65a0d9b097eb6a5a204dd0f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 6 Sep 2021 21:38:37 +0200 Subject: [PATCH 0212/1254] added tests for RelationshipType 'byEntityType' end point --- .../RelationshipTypeRestRepositoryIT.java | 33 +++++++++++++++++++ .../rest/matcher/RelationshipTypeMatcher.java | 9 +++++ 2 files changed, 42 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java index 8b117c5a31..edae721c19 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java @@ -254,5 +254,38 @@ public class RelationshipTypeRestRepositoryIT extends AbstractEntityIntegrationT } + @Test + public void findByEntityTypePublicationTest() throws Exception { + getClient().perform(get("/api/core/relationshiptypes/search/byEntityType") + .param("type", "Publication")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(1, + "isAuthorOfPublication", "isPublicationOfAuthor"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(2, + "isProjectOfPublication", "isPublicationOfProject"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(3, + "isOrgUnitOfPublication", "isPublicationOfOrgUnit"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(10, + "isAuthorOfPublication", "isPublicationOfAuthor"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(11, + "isPublicationOfJournalIssue", "isJournalIssueOfPublication") + ))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + } + + @Test + public void findByEntityTypeMissingParamTest() throws Exception { + + getClient().perform(get("/api/core/relationshiptypes/search/byEntityType")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findByEntityTypeInvalidEntityTypeTest() throws Exception { + getClient().perform(get("/api/core/relationshiptypes/search/byEntityType") + .param("type", "WrongEntityType")) + .andExpect(status().isBadRequest()); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java index 4d46fe9f2e..7c37196514 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java @@ -89,4 +89,13 @@ public class RelationshipTypeMatcher { )) ); } + + public static Matcher matchExplicitRestrictedRelationshipTypeValues( + int id, String leftwardType, String rightwardType) { + return allOf(hasJsonPath("$.id", is(id)), + hasJsonPath("$.leftwardType", is(leftwardType)), + hasJsonPath("$.rightwardType", is(rightwardType)) + ); + } + } \ No newline at end of file From ab7df25d0c89a15e66df9424656e737e265c8b7a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 6 Sep 2021 21:40:05 +0200 Subject: [PATCH 0213/1254] implemented 'byEntityType' end point in RelationshipTypeRestRepository --- .../RelationshipTypeRestRepository.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java index 85632f709a..ce56143179 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java @@ -9,9 +9,15 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; import java.util.List; +import java.util.Objects; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.RelationshipTypeRest; +import org.dspace.content.EntityType; import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +35,9 @@ public class RelationshipTypeRestRepository extends DSpaceRestRepository findByEntityType(@Parameter(value = "type", required = true) String type, + Pageable pageable) throws SQLException { + Context context = obtainContext(); + EntityType entityType = entityTypeService.findByEntityType(context, type); + if (Objects.isNull(entityType)) { + throw new DSpaceBadRequestException("EntityType with name: " + type + " not found"); + } + List relationshipTypes = relationshipTypeService.findByEntityType(context, entityType, + Math.toIntExact(pageable.getPageSize()), Math.toIntExact(pageable.getOffset())); + return converter.toRestPage(relationshipTypes, pageable, utils.obtainProjection()); + } + @Override public Class getDomainClass() { return RelationshipTypeRest.class; From 5ac2c0325d2a164f21909675fe861a584cbf5575 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 6 Sep 2021 22:12:08 +0200 Subject: [PATCH 0214/1254] added tests for search 'byItemsAndType' end point in RelationshipRest repository --- .../rest/RelationshipRestRepositoryIT.java | 108 ++++++++++++++++++ .../app/rest/matcher/RelationshipMatcher.java | 8 ++ 2 files changed, 116 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index b64a5c95a8..3475ad9c19 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -2921,4 +2921,112 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest ))); } + @Test + public void findByItemsAndTypeTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndTypeName(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + Relationship relationship1 = RelationshipBuilder.createRelationshipBuilder(context, publication1, author3, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(1) + .build(); + Relationship relationship2 = RelationshipBuilder.createRelationshipBuilder(context, publication1, author1, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(1) + .build(); + + context.restoreAuthSystemState(); + + // by left relation + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", "1") + .param("relationshipLabel", "isAuthorOfPublication") + .param("focusItem", publication1.getID().toString()) + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationshipValues(relationship1), + RelationshipMatcher.matchRelationshipValues(relationship2) + ))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // by right relation + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", "1") + .param("relationshipLabel", "isPublicationOfAuthor") + .param("focusItem", author1.getID().toString()) + .param("relatedItem", publication1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships", contains( + RelationshipMatcher.matchRelationshipValues(relationship2) + ))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + + @Test + public void findByItemsAndTypeBadRequestTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndTypeName(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, author3, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(1) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, author1, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(1) + .build(); + + context.restoreAuthSystemState(); + + // missing relationshipLabel + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", "1") + .param("focusItem", publication1.getID().toString()) + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isBadRequest()); + + // missing typeId + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("relationshipLabel", "isAuthorOfPublication") + .param("focusItem", publication1.getID().toString()) + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isBadRequest()); + + // missing focusItem + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", "1") + .param("relationshipLabel", "isAuthorOfPublication") + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isBadRequest()); + + // missing relatedItem + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", "1") + .param("relationshipLabel", "isAuthorOfPublication") + .param("focusItem", publication1.getID().toString())) + .andExpect(status().isBadRequest()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java index 068788a546..483709db69 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java @@ -50,6 +50,14 @@ public class RelationshipMatcher { ); } + public static Matcher matchRelationshipValues(Relationship relationship) { + return allOf( + hasJsonPath("$._links.leftItem.href", containsString(relationship.getLeftItem().getID().toString())), + hasJsonPath("$._links.rightItem.href", containsString(relationship.getRightItem().getID().toString())), + hasJsonPath("$.leftPlace", is(relationship.getLeftPlace())), + hasJsonPath("$.rightPlace", is(relationship.getRightPlace()))); + } + /** * Gets a matcher for all expected embeds when the full projection is requested. */ From e068e94f6d63330aa9461e22a99e06691106de04 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 6 Sep 2021 22:13:50 +0200 Subject: [PATCH 0215/1254] implemented search 'byItemsAndType' end point for RelationshipRest repository --- .../content/RelationshipServiceImpl.java | 8 ++++++ .../dspace/content/dao/RelationshipDAO.java | 14 ++++++++++ .../content/dao/impl/RelationshipDAOImpl.java | 17 ++++++++++++ .../content/service/RelationshipService.java | 15 +++++++++++ .../RelationshipRestRepository.java | 27 +++++++++++++++++++ 5 files changed, 81 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index 1b419da816..c7971716a4 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -12,6 +12,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; +import java.util.UUID; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -707,4 +708,11 @@ public class RelationshipServiceImpl implements RelationshipService { throws SQLException { return relationshipDAO.countByTypeName(context, typeName); } + + @Override + public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, + RelationshipType relationshipType, List items, boolean isLeft) throws SQLException { + return relationshipDAO + .findByItemAndRelationshipTypeAndList(context, focusUUID, relationshipType, items, isLeft); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java index e28cd0b6ac..0e877d9913 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java @@ -9,6 +9,7 @@ package org.dspace.content.dao; import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.dspace.content.Item; import org.dspace.content.Relationship; @@ -215,4 +216,17 @@ public interface RelationshipDAO extends GenericDAO { */ int countByTypeName(Context context, String typeName) throws SQLException; + + /** + * @param context DSpace context object + * @param focusUUID UUID of Item that will match left side if the param isLeft is true otherwise right side + * @param relationshipType Relationship type to filter by + * @param items List of UUID that will use to filter other side respect the focusUUID + * @param isLeft Indicating whether the counted Relationships should have + * the given Item on the left side or not + * @return + * @throws SQLException If database error + */ + List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, + RelationshipType relationshipType, List items, boolean isLeft) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index db1aef96a2..e9bffc0d13 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -10,6 +10,8 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.UUID; +import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; @@ -264,4 +266,19 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl return count(context, criteriaQuery, criteriaBuilder, relationshipRoot); } + @Override + public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, + RelationshipType relationshipType, List items, boolean isLeft) throws SQLException { + String side = isLeft ? "left_id" : "right_id"; + String otherSide = !isLeft ? "left_id" : "right_id"; + Query query = createQuery(context, "FROM " + Relationship.class.getSimpleName() + + " WHERE type_id = (:typeId) " + + "AND " + side + " = (:focusUUID) " + + "AND " + otherSide + " in (:list)"); + query.setParameter("typeId", relationshipType.getID()); + query.setParameter("focusUUID", focusUUID); + query.setParameter("list", items); + return list(query); + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index fab4616ef3..9223fe17fc 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -9,6 +9,7 @@ package org.dspace.content.service; import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; @@ -335,4 +336,18 @@ public interface RelationshipService extends DSpaceCRUDService { */ void forceDelete(Context context, Relationship relationship, boolean copyToLeftItem, boolean copyToRightItem) throws SQLException, AuthorizeException; + + /** + * @param context DSpace context object + * @param focusUUID UUID of Item that will match left side if the param isLeft is true otherwise right side + * @param relationshipType Relationship type to filter by + * @param items List of UUID that will use to filter other side respect the focusUUID + * @param isLeft Indicating whether the counted Relationships should have + * the given Item on the left side or not + * @return + * @throws SQLException If database error + */ + public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, + RelationshipType relationshipType, List items, boolean isLeft) throws SQLException; + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java index d1483a6d7a..3bcb57bd74 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -9,8 +9,11 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -367,4 +370,28 @@ public class RelationshipRestRepository extends DSpaceRestRepository findByItemsAndType( + @Parameter(value = "typeId", required = true) Integer typeId, + @Parameter(value = "relationshipLabel", required = true) String label, + @Parameter(value = "focusItem", required = true) UUID focusUUID, + @Parameter(value = "relatedItem", required = true) Set items, + Pageable pageable) throws SQLException { + Context context = obtainContext(); + List relationships = new LinkedList<>(); + RelationshipType relationshipType = relationshipTypeService.find(context, typeId); + if (Objects.nonNull(relationshipType)) { + if (!relationshipType.getLeftwardType().equals(label) && + !relationshipType.getRightwardType().equals(label)) { + throw new UnprocessableEntityException("The provided label: " + label + + " , does not match any relation!"); + } + relationships = relationshipService.findByItemAndRelationshipTypeAndList(context, focusUUID, + relationshipType, new ArrayList(items), relationshipType.getLeftwardType().equals(label)); + + } + return converter.toRestPage(relationships, pageable, utils.obtainProjection()); + } + } From 70f7c8b5abd08e29ba6ae56fe1220a01cad0395e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 7 Sep 2021 09:22:20 +0100 Subject: [PATCH 0216/1254] renaming properties and improve file documentation --- .../main/java/org/dspace/external/OpenAIRERestConnector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2d39786264..f748894fe9 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -331,8 +331,8 @@ public class OpenAIRERestConnector { * @param tokenUsage */ @Autowired(required = false) - public void setTokenUsage(boolean tokenUsage) { - this.tokenEnabled = tokenUsage; + public void setTokenEnabled(boolean tokenEnabled) { + this.tokenEnabled = tokenEnabled; } protected void getGotError(Exception e, String fullPath) { From eadadc05d7167786db615ee5b073f223513af049 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 7 Sep 2021 11:56:27 +0200 Subject: [PATCH 0217/1254] fix test --- .../RelationshipTypeRestRepositoryIT.java | 20 +++++++++---------- .../rest/matcher/RelationshipTypeMatcher.java | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java index edae721c19..f32e162b53 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java @@ -260,16 +260,16 @@ public class RelationshipTypeRestRepositoryIT extends AbstractEntityIntegrationT .param("type", "Publication")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( - RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(1, - "isAuthorOfPublication", "isPublicationOfAuthor"), - RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(2, - "isProjectOfPublication", "isPublicationOfProject"), - RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(3, - "isOrgUnitOfPublication", "isPublicationOfOrgUnit"), - RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(10, - "isAuthorOfPublication", "isPublicationOfAuthor"), - RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues(11, - "isPublicationOfJournalIssue", "isJournalIssueOfPublication") + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isAuthorOfPublication", "isPublicationOfAuthor"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isProjectOfPublication", "isPublicationOfProject"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isOrgUnitOfPublication", "isPublicationOfOrgUnit"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isAuthorOfPublication", "isPublicationOfAuthor"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isPublicationOfJournalIssue", "isJournalIssueOfPublication") ))) .andExpect(jsonPath("$.page.totalElements", is(5))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java index 7c37196514..4a3f8ea73f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java @@ -91,8 +91,8 @@ public class RelationshipTypeMatcher { } public static Matcher matchExplicitRestrictedRelationshipTypeValues( - int id, String leftwardType, String rightwardType) { - return allOf(hasJsonPath("$.id", is(id)), + String leftwardType, String rightwardType) { + return allOf( hasJsonPath("$.leftwardType", is(leftwardType)), hasJsonPath("$.rightwardType", is(rightwardType)) ); From cb7e51d68738ddd2502d81a4dab17f5588ccb646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 7 Sep 2021 11:05:06 +0100 Subject: [PATCH 0218/1254] add support for funding source at openaire project form --- dspace/config/submission-forms.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 878b79ffd6..7530a097ce 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -1066,7 +1066,7 @@ relation onebox - + openAIREFunding From 4d913ecf36d282efe84ff4c392894a1a573b30d3 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 7 Sep 2021 12:34:56 +0200 Subject: [PATCH 0219/1254] fix test --- .../org/dspace/app/rest/RelationshipRestRepositoryIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index 3475ad9c19..0cb731930a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -2944,7 +2944,7 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest // by left relation getClient().perform(get("/api/core/relationships/search/byItemsAndType") - .param("typeId", "1") + .param("typeId", isAuthorOfPublicationRelationshipType.getID().toString()) .param("relationshipLabel", "isAuthorOfPublication") .param("focusItem", publication1.getID().toString()) .param("relatedItem", author1.getID().toString(), @@ -2960,7 +2960,7 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest // by right relation getClient().perform(get("/api/core/relationships/search/byItemsAndType") - .param("typeId", "1") + .param("typeId", isAuthorOfPublicationRelationshipType.getID().toString()) .param("relationshipLabel", "isPublicationOfAuthor") .param("focusItem", author1.getID().toString()) .param("relatedItem", publication1.getID().toString())) From 4b23d89727c9d1ffaa03bea4579d49918f956bea Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 7 Sep 2021 13:25:45 +0200 Subject: [PATCH 0220/1254] 83338: Add ITs for DSO metadata move operations This commit should confirm that there's an issue with multi-op metadata move PATCH requests: DSpaceObjectServiceImpl.moveMetadata doesn't keep track of the changed order & messes up subsequent moves --- .../org/dspace/app/rest/PatchMetadataIT.java | 254 +++++++++++++++--- 1 file changed, 224 insertions(+), 30 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java index 7c57e98f1c..e35eb07fa3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PatchMetadataIT.java @@ -76,7 +76,8 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { private WorkspaceItemService workspaceItemService; private Collection collection; - private WorkspaceItem publicationItem; + private WorkspaceItem publicationWorkspaceItem; + private Item publicationItem; private Item personItem1; private Item personItem2; private RelationshipType publicationPersonRelationshipType; @@ -151,10 +152,10 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .withPersonIdentifierLastName("Linton") .withEntityType("Person") .build(); - publicationItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) - .withTitle("Publication 1") - .withEntityType("Publication") - .build(); + publicationWorkspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Publication 1") + .withEntityType("Publication") + .build(); publicationPersonRelationshipType = relationshipTypeService.findbyTypesAndTypeName(context, entityTypeService.findByEntityType(context, "Publication"), entityTypeService.findByEntityType(context, "Person"), @@ -164,7 +165,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { String adminToken = getAuthToken(admin.getEmail(), password); // Make sure we grab the latest instance of the Item from the database before adding a regular author - WorkspaceItem publication = workspaceItemService.find(context, publicationItem.getID()); + WorkspaceItem publication = workspaceItemService.find(context, publicationWorkspaceItem.getID()); itemService.addMetadata(context, publication.getItem(), "dc", "contributor", "author", Item.ANY, authorsOriginalOrder.get(0)); workspaceItemService.update(context, publication); @@ -177,7 +178,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .param("relationshipType", publicationPersonRelationshipType.getID().toString()) .contentType(MediaType.parseMediaType (org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) - .content("https://localhost:8080/server/api/core/items/" + publicationItem.getItem().getID() + "\n" + + .content("https://localhost:8080/server/api/core/items/" + publicationWorkspaceItem.getItem().getID() + "\n" + "https://localhost:8080/server/api/core/items/" + personItem1.getID())) .andExpect(status().isCreated()) .andDo(result -> idRef1.set(read(result.getResponse().getContentAsString(), "$.id"))); @@ -185,7 +186,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { // Add two more regular authors List regularMetadata = new ArrayList<>(); - publication = workspaceItemService.find(context, publicationItem.getID()); + publication = workspaceItemService.find(context, publicationWorkspaceItem.getID()); regularMetadata.add(authorsOriginalOrder.get(2)); regularMetadata.add(authorsOriginalOrder.get(3)); itemService.addMetadata(context, publication.getItem(), @@ -200,12 +201,12 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { .param("relationshipType", publicationPersonRelationshipType.getID().toString()) .contentType(MediaType.parseMediaType (org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) - .content("https://localhost:8080/server/api/core/items/" + publicationItem.getItem().getID() + "\n" + + .content("https://localhost:8080/server/api/core/items/" + publicationWorkspaceItem.getItem().getID() + "\n" + "https://localhost:8080/server/api/core/items/" + personItem2.getID())) .andExpect(status().isCreated()) .andDo(result -> idRef2.set(read(result.getResponse().getContentAsString(), "$.id"))); - publication = workspaceItemService.find(context, publicationItem.getID()); + publication = workspaceItemService.find(context, publicationWorkspaceItem.getID()); authorsMetadataOriginalOrder = itemService.getMetadata(publication.getItem(), "dc", "contributor", "author", Item.ANY); assertEquals(authorsMetadataOriginalOrder.size(), 5); @@ -221,6 +222,35 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { assertThat(authorsMetadataOriginalOrder.get(4).getAuthority(), startsWith("virtual::")); } + /** + * A method to create a simple Item with 5 authors + */ + private void initSimplePublicationItem() throws Exception { + // Setup the original order of authors + authorsOriginalOrder = new ArrayList<>(); + authorsOriginalOrder.add("Whyte, William"); + authorsOriginalOrder.add("Dahlen, Sarah"); + authorsOriginalOrder.add("Peterson, Karrie"); + authorsOriginalOrder.add("Perotti, Enrico"); + authorsOriginalOrder.add("Linton, Oliver"); + authorsOriginalOrder.add("bla, Oliver"); + + context.turnOffAuthorisationSystem(); + + publicationItem = ItemBuilder.createItem(context, collection) + .withTitle("Publication 1") + .withEntityType("Publication") + .build(); + + for (String author : authorsOriginalOrder) { + itemService.addMetadata( + context, publicationItem, "dc", "contributor", "author", Item.ANY, author + ); + } + + context.restoreAuthSystemState(); + } + /** * Clean up created Person Relationshipts * @throws IOException @@ -238,7 +268,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { } /** - * A method to create a workspace publication containing 5 authors: 3 regular authors and 2 related Person items. + * A method to create a publication Item containing 5 authors: 3 regular authors and 2 related Person items. * The authors are added in a specific order: * - "Whyte, William": Regular author * - "Dahlen, Sarah": Regular Person @@ -259,22 +289,22 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { context.turnOffAuthorisationSystem(); - publicationItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) - .withTitle("Publication 1") - .withEntityType("Publication") - .build(); + publicationWorkspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Publication 1") + .withEntityType("Publication") + .build(); String adminToken = getAuthToken(admin.getEmail(), password); // Make sure we grab the latest instance of the Item from the database before adding a regular author - WorkspaceItem publication = workspaceItemService.find(context, publicationItem.getID()); + WorkspaceItem publication = workspaceItemService.find(context, publicationWorkspaceItem.getID()); itemService.addMetadata(context, publication.getItem(), "dc", "contributor", "author", Item.ANY, authorsOriginalOrder); workspaceItemService.update(context, publication); context.restoreAuthSystemState(); - publication = workspaceItemService.find(context, publicationItem.getID()); + publication = workspaceItemService.find(context, publicationWorkspaceItem.getID()); List publicationAuthorList = itemService.getMetadata(publication.getItem(), "dc", "contributor", "author", Item.ANY); assertEquals(publicationAuthorList.size(), 5); @@ -1147,13 +1177,13 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", @@ -1214,13 +1244,138 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isUnprocessableEntity()); } + /** + * This test will move an Item's dc.contributor.author value from position 1 to 2 using a PATCH request with + * a single move operation. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,2,1,3,4 + */ + @Test + public void moveMetadataAuthorOneToTwoTest() throws Exception { + initSimplePublicationItem(); + + List expectedOrder = List.of( + authorsOriginalOrder.get(0), + authorsOriginalOrder.get(2), + authorsOriginalOrder.get(1), + authorsOriginalOrder.get(3), + authorsOriginalOrder.get(4) + ); + List moves = List.of( + getMetadataMoveAuthorOperation(1, 2) + ); + + moveMetadataAuthorTest(moves, expectedOrder); + } + + /** + * This test will move an Item's dc.contributor.author value from position 2 to 1 using a PATCH request with + * a single move operation. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,2,1,3,4 + */ + @Test + public void moveMetadataAuthorTwoToOneTest() throws Exception { + initSimplePublicationItem(); + + List expectedOrder = List.of( + authorsOriginalOrder.get(0), + authorsOriginalOrder.get(2), + authorsOriginalOrder.get(1), + authorsOriginalOrder.get(3), + authorsOriginalOrder.get(4) + ); + List moves = List.of( + getMetadataMoveAuthorOperation(2, 1) + ); + + moveMetadataAuthorTest(moves, expectedOrder); + } + + /** + * This test will move an Item's dc.contributor.author value from position 1 to 4 using a PATCH request with + * a single move operation. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,2,3,4,1 + */ + @Test + public void moveMetadataAuthorOneToFourTest() throws Exception { + initSimplePublicationItem(); + + List expectedOrder = List.of( + authorsOriginalOrder.get(0), + authorsOriginalOrder.get(2), + authorsOriginalOrder.get(3), + authorsOriginalOrder.get(4), + authorsOriginalOrder.get(1) + ); + List moves = List.of( + getMetadataMoveAuthorOperation(1, 4) + ); + + moveMetadataAuthorTest(moves, expectedOrder); + } + + /** + * This test will move an Item's dc.contributor.author value from position 4 to 1 using a PATCH request with + * a single move operation. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,4,1,2,3 + */ + @Test + public void moveMetadataAuthorFourToOneTest() throws Exception { + initSimplePublicationItem(); + + List expectedOrder = List.of( + authorsOriginalOrder.get(0), + authorsOriginalOrder.get(4), + authorsOriginalOrder.get(1), + authorsOriginalOrder.get(2), + authorsOriginalOrder.get(3) + ); + List moves = List.of( + getMetadataMoveAuthorOperation(4, 1) + ); + + moveMetadataAuthorTest(moves, expectedOrder); + } + + /** + * This test will move an Item's dc.contributor.author value from position 4 to 1 using a PATCH request with + * multiple move operations and verify the order of the authors within the section. + * The move operations are equivalent to a regular 4 to 1 move and representative of the kind of PATCH request the + * frontend actually sends in this kind of scenario. + * Original Order: 0,1,2,3,4 + * Expected Order: 0,4,1,2,3 + */ + @Test + public void moveMetadataAuthorFourToOneMultiOpTest() throws Exception { + initSimplePublicationItem(); + + List expectedOrder = List.of( + authorsOriginalOrder.get(0), + authorsOriginalOrder.get(4), + authorsOriginalOrder.get(1), + authorsOriginalOrder.get(2), + authorsOriginalOrder.get(3) + ); + List moves = List.of( + getMetadataMoveAuthorOperation(1, 2), + getMetadataMoveAuthorOperation(1, 3), + getMetadataMoveAuthorOperation(2, 4), + getMetadataMoveAuthorOperation(3, 1) + ); + + moveMetadataAuthorTest(moves, expectedOrder); + } + /** * This method moves an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section from position "from" to "path" using a PATCH request and verifies the order of the authors within the @@ -1237,13 +1392,13 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( @@ -1255,6 +1410,35 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { ))); } + /** + * This method rearranges an Item's dc.contributor.author values using multiple PATCH request and verifies the order + * of the authors within the section using an ordered list of expected author names. + * @param moves A list of move operations + * @param expectedOrder A list of author names sorted in the expected order + */ + private void moveMetadataAuthorTest(List moves, List expectedOrder) throws Exception { + String patchBody = getPatchContent(moves); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(patch("/api/core/items/" + publicationItem.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + String authorField = "dc.contributor.author"; + getClient(token).perform(get("/api/core/items/" + publicationItem.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.metadata", Matchers.allOf( + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(0), 0)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(1), 1)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(2), 2)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(3), 3)), + Matchers.is(MetadataMatcher.matchMetadata(authorField, expectedOrder.get(4), 4)) + ))); + } + /** * This method replaces an author (dc.contributor.author) within a workspace publication's "traditionalpageone" * section at position "path" using a PATCH request and verifies the order of the authors within the @@ -1274,13 +1458,13 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( @@ -1310,13 +1494,13 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( @@ -1345,13 +1529,13 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()); String authorField = "dc.contributor.author"; - getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf( @@ -1388,7 +1572,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationItem.getID()) + getClient(token).perform(patch("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()); @@ -1400,7 +1584,7 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { }); - getClient(token).perform(get("/api/submission/workspaceitems/" + publicationItem.getID())) + getClient(token).perform(get("/api/submission/workspaceitems/" + publicationWorkspaceItem.getID())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.sections.traditionalpageone", Matchers.allOf(matchers))); @@ -1417,4 +1601,14 @@ public class PatchMetadataIT extends AbstractEntityIntegrationTest { "/sections/traditionalpageone/dc.contributor.author/" + from); } + /** + * Create a move operation on an Item's metadata field "dc.contributor.author". + * @param from The "from" index to use for the Move operation + * @param path The "path" index to use for the Move operation + */ + private MoveOperation getMetadataMoveAuthorOperation(int from, int path) { + return new MoveOperation("/metadata/dc.contributor.author/" + path, + "/metadata/dc.contributor.author/" + from); + } + } From f0079d755522d3ec4ed1ad5167e9b306e86f4594 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 7 Sep 2021 13:51:32 +0200 Subject: [PATCH 0221/1254] 83338: Fix moveMetadata index caching issue --- .../java/org/dspace/content/DSpaceObjectServiceImpl.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index c34291c3dd..afb2deb332 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.NotImplementedException; @@ -742,12 +743,15 @@ public abstract class DSpaceObjectServiceImpl implements @Override public void moveMetadata(Context context, T dso, String schema, String element, String qualifier, int from, int to) throws SQLException, IllegalArgumentException { - if (from == to) { throw new IllegalArgumentException("The \"from\" location MUST be different from \"to\" location"); } - List list = getMetadata(dso, schema, element, qualifier); + List list = + getMetadata(dso, schema, element, qualifier).stream() + .sorted(Comparator.comparing(MetadataValue::getPlace)) + .collect(Collectors.toList()); + if (from >= list.size() || to >= list.size() || to < 0 || from < 0) { throw new IllegalArgumentException( From 4644b1f18c7061dccf7d900994a51443b55aa642 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 Sep 2021 12:57:29 -0500 Subject: [PATCH 0222/1254] Fix order of Builder cleanup. Ensure all Items are deleted *before* Collections or Communities. --- .../org/dspace/builder/util/AbstractBuilderCleanupUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index 82555c2d37..3c05777b56 100644 --- a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -65,12 +65,12 @@ public class AbstractBuilderCleanupUtil { map.put(BitstreamBuilder.class.getName(), new LinkedList<>()); map.put(BitstreamFormatBuilder.class.getName(), new LinkedList<>()); map.put(ClaimedTaskBuilder.class.getName(), new LinkedList<>()); - map.put(CollectionBuilder.class.getName(), new LinkedList<>()); - map.put(CommunityBuilder.class.getName(), new LinkedList<>()); map.put(EPersonBuilder.class.getName(), new LinkedList<>()); map.put(GroupBuilder.class.getName(), new LinkedList<>()); map.put(BundleBuilder.class.getName(), new LinkedList<>()); map.put(ItemBuilder.class.getName(), new LinkedList<>()); + map.put(CollectionBuilder.class.getName(), new LinkedList<>()); + map.put(CommunityBuilder.class.getName(), new LinkedList<>()); map.put(MetadataFieldBuilder.class.getName(), new LinkedList<>()); map.put(MetadataSchemaBuilder.class.getName(), new LinkedList<>()); map.put(SiteBuilder.class.getName(), new LinkedList<>()); From 7e969e1669c87e5a48e2b99e9cf29133b158e82c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 Sep 2021 13:08:01 -0500 Subject: [PATCH 0223/1254] Delete groups and people after Collections/Communities, as Comm/Coll groups must be cleaned up first. --- .../org/dspace/builder/util/AbstractBuilderCleanupUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index 3c05777b56..eedad4345b 100644 --- a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -65,12 +65,12 @@ public class AbstractBuilderCleanupUtil { map.put(BitstreamBuilder.class.getName(), new LinkedList<>()); map.put(BitstreamFormatBuilder.class.getName(), new LinkedList<>()); map.put(ClaimedTaskBuilder.class.getName(), new LinkedList<>()); - map.put(EPersonBuilder.class.getName(), new LinkedList<>()); - map.put(GroupBuilder.class.getName(), new LinkedList<>()); map.put(BundleBuilder.class.getName(), new LinkedList<>()); map.put(ItemBuilder.class.getName(), new LinkedList<>()); map.put(CollectionBuilder.class.getName(), new LinkedList<>()); map.put(CommunityBuilder.class.getName(), new LinkedList<>()); + map.put(GroupBuilder.class.getName(), new LinkedList<>()); + map.put(EPersonBuilder.class.getName(), new LinkedList<>()); map.put(MetadataFieldBuilder.class.getName(), new LinkedList<>()); map.put(MetadataSchemaBuilder.class.getName(), new LinkedList<>()); map.put(SiteBuilder.class.getName(), new LinkedList<>()); From 52bfcafca166f41323a1a195bd8ce1f62c220760 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 8 Sep 2021 15:35:48 +0200 Subject: [PATCH 0224/1254] refactored versions end point --- .../exception/DSpaceForbiddenException.java | 28 ++++ .../repository/VersionRestRepository.java | 127 +++++++----------- ...ionRestPatchPermissionEvaluatorPlugin.java | 83 ++++++++++++ 3 files changed, 159 insertions(+), 79 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceForbiddenException.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceForbiddenException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceForbiddenException.java new file mode 100644 index 0000000000..df385f6602 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceForbiddenException.java @@ -0,0 +1,28 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Forbidden request") +public class DSpaceForbiddenException extends RuntimeException { + + private static final long serialVersionUID = 8869331967657914409L; + + public DSpaceForbiddenException(String message, Throwable cause) { + super(message, cause); + } + + public DSpaceForbiddenException(String message) { + super(message); + } + +} \ No newline at end of file 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 a2850582ee..254fa170d8 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 @@ -7,19 +7,15 @@ */ package org.dspace.app.rest.repository; import java.sql.SQLException; -import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.UUID; import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.NotFoundException; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.Logger; -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.DSpaceForbiddenException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.VersionRest; @@ -27,14 +23,18 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.handler.service.UriListHandlerService; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; -import org.dspace.content.service.ItemService; +import org.dspace.content.WorkspaceItem; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; +import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -53,21 +53,24 @@ public class VersionRestRepository extends DSpaceRestRepository stringList) throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String summary = req.getParameter("summary"); + Item item = uriListHandlerService.handle(context, req, stringList, Item.class); if (Objects.isNull(item)) { throw new UnprocessableEntityException("The given URI list could not be properly parsed to one result"); } - if (Objects.nonNull(workspaceItemService.findByItem(context, item)) || - Objects.nonNull(workflowItemService.findByItem(context, item))) { + + EPerson submitter = item.getSubmitter(); + boolean isAdmin = authorizeService.isAdmin(context); + boolean canCreateVersion = configurationService.getBooleanProperty("versioning.submitterCanCreateNewVersion"); + if (!isAdmin && !(canCreateVersion && Objects.equals(submitter, context.getCurrentUser()))) { + throw new DSpaceForbiddenException(""); + } + + WorkflowItem workflowItem = null; + WorkspaceItem workspaceItem = null; + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + if (Objects.nonNull(versionHistory)) { + Version lastVersion = versionHistoryService.getLatestVersion(context, versionHistory); + if (Objects.nonNull(lastVersion)) { + workflowItem = workflowItemService.findByItem(context, lastVersion.getItem()); + workspaceItem = workspaceItemService.findByItem(context, lastVersion.getItem()); + } + } else { + workflowItem = workflowItemService.findByItem(context, item); + workspaceItem = workspaceItemService.findByItem(context, item); + } + + if (Objects.nonNull(workflowItem) || Objects.nonNull(workspaceItem)) { throw new UnprocessableEntityException(""); } - Version version = null; - if (StringUtils.isNotBlank(summary)) { - version = versioningService.createNewVersion(context, item, summary); - } else { - version = versioningService.createNewVersion(context, item); - } + + Version version = StringUtils.isNotBlank(summary) ? + versioningService.createNewVersion(context, item, summary) : + versioningService.createNewVersion(context, item); return converter.toRest(version, utils.obtainProjection()); } - @PreAuthorize("hasAuthority('ADMIN')") - @SearchRestMethod(name = "findByItem") - public VersionRest findByItem(@Parameter(value = "itemUuid", required = true) UUID itemUuid) { - Context context = obtainContext(); - Version version = null; - try { - Item item = itemService.find(context, itemUuid); - if (Objects.nonNull(item)) { - version = versioningService.getVersion(context, item); - } - } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } - return Objects.nonNull(version) ? converter.toRest(version, utils.obtainProjection()) : null; - } - - @PreAuthorize("hasAuthority('ADMIN')") - @SearchRestMethod(name = "findByHistory") - public Page findByHistory(@Parameter(value = "historyId", required = true) Integer historyId, - Pageable pageable) { - Context context = obtainContext(); - List versions = new LinkedList();; - int total = 0; - try { - VersionHistory versionHistory = versionHistoryService.find(context, historyId); - if (Objects.isNull(versionHistory)) { - throw new DSpaceBadRequestException( - "This given id:" + historyId + " does not resolve to a VersionHistory"); - } - versions = versioningService.getVersionsByHistory(context, versionHistory, - Math.toIntExact(pageable.getOffset()), - Math.toIntExact(pageable.getPageSize())); - total = versioningService.countVersionsByHistory(context, versionHistory); - } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } - return converter.toRestPage(versions, pageable, total, utils.obtainProjection()); - } - @Override - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("hasPermission(#versionId, 'version', 'ADMIN')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer versionId, Patch patch) throws AuthorizeException, SQLException { for (Operation operation : patch.getOperations()) { @@ -180,7 +148,7 @@ public class VersionRestRepository extends DSpaceRestRepository getDomainClass() { return VersionRest.class; } -} + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..84490df457 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java @@ -0,0 +1,83 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; +import java.io.Serializable; +import java.sql.SQLException; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.VersionRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.dspace.versioning.Version; +import org.dspace.versioning.service.VersioningService; +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 at 4science.it) + */ +@Component +public class VersionRestPatchPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(VersionRestPatchPermissionEvaluatorPlugin.class); + + @Autowired + private RequestService requestService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private VersioningService versioningService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission permission) { + + DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); + + if (!DSpaceRestPermission.ADMIN.equals(restPermission) || + !StringUtils.equalsIgnoreCase(targetType, VersionRest.NAME)) { + return false; + } + + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getServletRequest()); + + try { + if (targetId instanceof UUID) { + return false; + } + int versionId = Integer.parseInt(targetId.toString()); + + Version version = versioningService.getVersion(context, versionId); + if (version == null) { + return true; + } + + if (!authorizeService.isAdmin(context, version.getItem()) + && !authorizeService.isAdmin(context)) { + return false; + } + if (authorizeService.authorizeActionBoolean(context, version.getItem(), + restPermission.getDspaceApiActionId())) { + return true; + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return false; + } +} \ No newline at end of file From 80e34e534964ba48a5e8dcb46dd1cfb7da0aff7f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 8 Sep 2021 15:36:39 +0200 Subject: [PATCH 0225/1254] refactored tests of version end point --- .../app/rest/VersionRestRepositoryIT.java | 773 ++++++++++-------- 1 file changed, 447 insertions(+), 326 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 da8dd0d753..1899390383 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 @@ -31,6 +31,7 @@ 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.ItemBuilder; import org.dspace.builder.VersionBuilder; import org.dspace.builder.WorkflowItemBuilder; @@ -41,6 +42,7 @@ import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; @@ -109,7 +111,13 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(adminToken).perform(get("/api/versioning/versions/" + version.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(version)))); + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(version)))) + .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + version.getID() + "/versionhistory")))) + .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + version.getID() + "/item")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + version.getID())))); } @Test @@ -227,35 +235,26 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isNotFound()); } - @Test - public void deleteVersionItemAdminTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(delete("/api/versioning/versions/" + version.getID())) - .andExpect(status().isNoContent()); - } - - @Test - public void deleteVersionItemUnauthorizedTest() throws Exception { - getClient().perform(delete("/api/versioning/versions/" + version.getID())) - .andExpect(status().isUnauthorized()); - } - - @Test - public void deleteVersionItemForbiddenTest() throws Exception { - String epersonToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonToken).perform(delete("/api/versioning/versions/" + version.getID())) - .andExpect(status().isForbidden()); - } - - @Test - public void deleteVersionItemNotFoundTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(delete("/api/versioning/versions/" + Integer.MAX_VALUE)) - .andExpect(status().isNotFound()); - } - @Test public void createFirstVersionItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/versioning/versions") .param("summary", "test summary!") @@ -263,7 +262,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .content("/api/core/items/" + item.getID())) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(3)), + hasJsonPath("$.version", is(2)), hasJsonPath("$.summary", is("test summary!")), hasJsonPath("$.submitterName", is("first (admin) last (admin)")), hasJsonPath("$.type", is("version")) @@ -318,7 +317,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10).build(); + Version v10 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10).build(); context.restoreAuthSystemState(); @@ -326,7 +325,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(adminToken).perform(post("/api/versioning/versions") .param("summary", "test summary.") .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) - .content("/api/core/items/" + item.getID())) + .content("/api/core/items/" + v10.getItem().getID())) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf( hasJsonPath("$.version", is(11)), @@ -345,7 +344,8 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); + .withName("Collection test") + .build(); Item item = ItemBuilder.createItem(context, col) .withTitle("Public test item") @@ -354,16 +354,8 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withIssueDate("2021-03-20") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 1).build(); - VersionBuilder.createVersionWithVersionHistory(context, item2, "tes2", versionHistory, 2).build(); + VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 2).build(); context.restoreAuthSystemState(); @@ -381,6 +373,54 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { ))); } + @Test + public void createVersionWithLastVersionInSubmissionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-03-20") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version v = VersionBuilder.createVersion(context, item, "test summary").build(); + Item item2 = v.getItem(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // check that the item is deposited + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(item.getID().toString()))) + .andExpect(jsonPath("$.withdrawn", Matchers.is(false))) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))); + + // check that the item2 is not deposited yet + getClient(adminToken).perform(get("/api/core/items/" + item2.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(item2.getID().toString()))) + .andExpect(jsonPath("$.withdrawn", Matchers.is(false))) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + // we can not create a new version because the item2 is in submission + getClient(adminToken).perform(post("/api/versioning/versions") + .param("summary", "check first version") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + item.getID())) + .andExpect(status().isUnprocessableEntity()); + } + @Test public void createVersionFromWorkflowItemTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -437,302 +477,74 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { } @Test - public void findByItemAdminTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versions/search/findByItem") - .param("itemUuid", item.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(1)), - hasJsonPath("$.summary", emptyString()), - hasJsonPath("$.submitterName", is("first last")), - hasJsonPath("$.type", is("version")) - ))); - } - - @Test - public void findByItemVersionUnauthorizedTest() throws Exception { - getClient().perform(get("/api/versioning/versions/search/findByItem") - .param("itemUuid", item.getID().toString())) - .andExpect(status().isUnauthorized()); - } - - @Test - public void findByItemNotYetVersionedTest() throws Exception { + public void createFirstVersionItemWithSubmitterTest() throws Exception { + configurationService.setProperty("versioning.submitterCanCreateNewVersion", true); context.turnOffAuthorisationSystem(); + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Sample 1 collection") - .withSubmitterGroup(eperson).build(); + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); - Item publicItem = ItemBuilder.createItem(context, col1) - .withTitle("Public item") - .withAuthor("Smith, Maria") - .withSubject("ExtraEntry") - .build(); + itemA.setSubmitter(eperson); context.restoreAuthSystemState(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versions/search/findByItem") - .param("itemUuid", publicItem.getID().toString())) - .andExpect(status().isNoContent()); - } - @Test - public void findByItemForbiddenTest() throws Exception { String epersonToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonToken).perform(get("/api/versioning/versions/search/findByItem") - .param("itemUuid", item.getID().toString())) - .andExpect(status().isForbidden()); + getClient(epersonToken).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + itemA.getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(2)), + hasJsonPath("$.summary", is("test summary!")), + hasJsonPath("$.type", is("version")) + ))); + + configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); } @Test - public void findByItemBadRequestTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - - // missing to provid item uuid - getClient(adminToken).perform(get("/api/versioning/versions/search/findByItem")) - .andExpect(status().isBadRequest()); - - // provided wrong uuid - getClient(adminToken).perform(get("/api/versioning/versions/search/findByItem") - .param("itemUuid", "wrong id")) - .andExpect(status().isBadRequest()); - } - - @Test - public void findByHistoryTest() throws Exception { + public void createFirstVersionItemWithSubmitterAndPropertyForSubmitterDisabledTest() throws Exception { + configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); context.turnOffAuthorisationSystem(); + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) .build(); - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) - .build(); - Version v98 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98) - .build(); - Version v99 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 3", versionHistory, 0) - .build(); + itemA.setSubmitter(eperson); context.restoreAuthSystemState(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") - .param("historyId", versionHistory.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", Matchers.contains( - VersionMatcher.matchEntry(v99), - VersionMatcher.matchEntry(v98), - VersionMatcher.matchEntry(v77) - ))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/search/findByHistory?historyId=" + versionHistory.getID())))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(1))) - .andExpect(jsonPath("$.page.number", is(0))); - } - - @Test - public void findByHistoryPaginationTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) - .build(); - Version v98 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98) - .build(); - Version v99 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 3", versionHistory, 0) - .build(); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") - .param("size", "1") - .param("historyId", versionHistory.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", Matchers.contains( - VersionMatcher.matchEntry(v99) - ))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=0"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=1"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=2"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) - .andExpect(jsonPath("$.page.number", is(0))); - - getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") - .param("page", "1") - .param("size", "1") - .param("historyId", versionHistory.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", Matchers.contains( - VersionMatcher.matchEntry(v98) - ))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=1"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=0"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=0"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.next.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=2"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=2"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) - .andExpect(jsonPath("$.page.number", is(1))); - - getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") - .param("page", "2") - .param("size", "1") - .param("historyId", versionHistory.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", Matchers.contains( - VersionMatcher.matchEntry(v77) - ))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=2"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.first.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=0"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=1"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$._links.last.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("page=2"), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) - .andExpect(jsonPath("$.page.number", is(2))); - } - - @Test - public void findByHistoryPaginationEmptyResponseTest() throws Exception { - context.turnOffAuthorisationSystem(); - VersionHistory versionHistory = versionHistoryService.create(context); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") - .param("size", "1") - .param("historyId", versionHistory.getID().toString())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions").doesNotExist()) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf( - Matchers.containsString("api/versioning/versions/search/findByHistory?"), - Matchers.containsString("historyId=" + versionHistory.getID()), - Matchers.containsString("size=1") - ))) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(0))) - .andExpect(jsonPath("$.page.number", is(0))); - } - - @Test - public void findByHistoryPaginationXXXTest() throws Exception { - Integer id = Integer.MAX_VALUE; - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versions/search/findByHistory") - .param("size", "1") - .param("historyId", id.toString())) - .andExpect(status().isBadRequest()); + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + itemA.getID())) + .andExpect(status().isForbidden()); } @Test @@ -779,9 +591,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( "api/versioning/versions/" + v77.getID()) ))) - .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID() + "/versionhistory" - )))) .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( "api/versioning/versions/" + v77.getID() + "/item") ))); @@ -831,9 +640,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( "api/versioning/versions/" + v77.getID()) ))) - .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID() + "/versionhistory" - )))) .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( "api/versioning/versions/" + v77.getID() + "/item") ))); @@ -884,14 +690,26 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( "api/versioning/versions/" + v77.getID()) ))) - .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID() + "/versionhistory" - )))) .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( "api/versioning/versions/" + v77.getID() + "/item") ))); } + @Test + public void patchVersionNotFoundTest() throws Exception { + String summary = "Test Summary!"; + List ops = new ArrayList(); + AddOperation replaceOperation = new AddOperation("/summary", summary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(patch("/api/versioning/versions/" + Integer.MAX_VALUE) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isNotFound()); + } + @Test public void patchAddSummaryBadRequestTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -966,4 +784,307 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isUnprocessableEntity()); } + @Test + public void patchReplaceVersionUnprocessableEntityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + + context.restoreAuthSystemState(); + + String newVersion = "133"; + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/version", newVersion); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void patchReplaceSummaryUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + + context.restoreAuthSystemState(); + + String newSummary = "New Summary"; + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/summary", newSummary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + getClient().perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void patchRemoveSummaryUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + RemoveOperation replaceOperation = new RemoveOperation("/summary"); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + getClient().perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void patchRemoveSummaryForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + + context.restoreAuthSystemState(); + + List ops = new ArrayList(); + RemoveOperation replaceOperation = new RemoveOperation("/summary"); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + public void patchReplaceSummaryByCollectionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection title") + .withAdminGroup(eperson) + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-03-08") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + + context.restoreAuthSystemState(); + + String newSummary = "New Summary"; + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/summary", newSummary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String colAdminToken = getAuthToken(eperson.getEmail(), password); + getClient(colAdminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.summary", is(newSummary)), + hasJsonPath("$.type", is("version")) + ))); + } + + @Test + public void patchReplaceSummaryByCommunityAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson adminCommA = EPersonBuilder.createEPerson(context) + .withEmail("adminCommA@mail.com") + .withPassword(password) + .build(); + + EPerson adminCommB = EPersonBuilder.createEPerson(context) + .withEmail("adminCommB@mail.com") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("subCommunity A") + .withAdminGroup(adminCommA) + .build(); + + CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("subCommunity B") + .withAdminGroup(adminCommB) + .build(); + + Collection col = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection title") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-03-08") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + + context.restoreAuthSystemState(); + + String newSummary = "New Summary"; + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/summary", newSummary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminCommAToken = getAuthToken(adminCommA.getEmail(), password); + String adminCommBToken = getAuthToken(adminCommB.getEmail(), password); + + getClient(adminCommBToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + getClient(adminCommAToken).perform(get("/api/versioning/versions/" + v77.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v77)))); + + getClient(adminCommAToken).perform(patch("/api/versioning/versions/" + v77.getID()) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(adminCommAToken).perform(get("/api/versioning/versions/" + v77.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.summary", is(newSummary)), + hasJsonPath("$.type", is("version")) + ))); + } + + @Test + public void deleteVersionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-03-20") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionHistory versionHistory = versionHistoryService.create(context); + Version v2 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 2).build(); + + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + Integer versionID = v2.getID(); + Item versionItem = v2.getItem(); + + getClient(adminToken).perform(get("/api/versioning/versions/" + versionID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v2)))) + .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v2.getID() + "/versionhistory")))) + .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v2.getID() + "/item")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v2.getID())))); + + // To delete a version you need to delete the item linked to it. + getClient(adminToken).perform(delete("/api/core/items/" + versionItem.getID())) + .andExpect(status().is(204)); + + getClient(adminToken).perform(get("/api/versioning/versions/" + versionID)) + .andExpect(status().isNotFound()); + } + } \ No newline at end of file From e6c9652883b451b4959f1342a1d5b3a22abb2f0f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 8 Sep 2021 18:09:12 +0200 Subject: [PATCH 0226/1254] refactored VersionHistory end point --- .../app/rest/model/VersionHistoryRest.java | 32 +++++----- ...onHistoryCurrentVersionLinkRepository.java | 61 ------------------- ...ionHistoryDraftVersionLinkRepository.java} | 12 ++-- ...ionHistoryOldestVersionLinkRepository.java | 61 ------------------- 4 files changed, 24 insertions(+), 142 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryCurrentVersionLinkRepository.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{VersionHistoryLastVersionLinkRepository.java => VersionHistoryDraftVersionLinkRepository.java} (83%) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryOldestVersionLinkRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java index a6f364874b..e93e131aad 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VersionHistoryRest.java @@ -6,7 +6,7 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.model; - +import com.fasterxml.jackson.annotation.JsonInclude; import org.dspace.app.rest.RestResourceController; /** @@ -18,28 +18,23 @@ import org.dspace.app.rest.RestResourceController; method = "getVersions" ), @LinkRest( - name = VersionHistoryRest.OLDEST_VERSION, - method = "getOldestVersion" - ), - @LinkRest( - name = VersionHistoryRest.CURRENT_VERSION, - method = "getCurrentVersion" - ), - @LinkRest( - name = VersionHistoryRest.LAST_VERSION, - method = "getLastVersion" + name = VersionHistoryRest.DRAFT_VERSION, + method = "getDraftVersion" ) }) public class VersionHistoryRest extends BaseObjectRest { + private static final long serialVersionUID = -6466315011690554740L; + private Integer id; + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean draftVersion; + public static final String NAME = "versionhistory"; public static final String CATEGORY = RestAddressableModel.VERSIONING; public static final String VERSIONS = "versions"; - public static final String OLDEST_VERSION = "oldestversion"; - public static final String CURRENT_VERSION = "currentversion"; - public static final String LAST_VERSION = "lastversion"; + public static final String DRAFT_VERSION = "draftVersion"; @Override public String getCategory() { @@ -73,4 +68,13 @@ public class VersionHistoryRest extends BaseObjectRest { public void setId(Integer id) { this.id = id; } + + public Boolean getDraftVersion() { + return draftVersion; + } + + public void setDraftVersion(Boolean draftVersion) { + this.draftVersion = draftVersion; + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryCurrentVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryCurrentVersionLinkRepository.java deleted file mode 100644 index 537d5bd5aa..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryCurrentVersionLinkRepository.java +++ /dev/null @@ -1,61 +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.Objects; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; - -import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.model.VersionHistoryRest; -import org.dspace.app.rest.model.VersionRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.core.Context; -import org.dspace.versioning.Version; -import org.dspace.versioning.VersionHistory; -import org.dspace.versioning.service.VersionHistoryService; -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; - -/** - * This is the Repository that takes care of the retrieval of the current Version object - * for a given {@link VersionHistory} - * - * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) - */ -@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.CURRENT_VERSION) -public class VersionHistoryCurrentVersionLinkRepository extends AbstractDSpaceRestRepository - implements LinkRestRepository { - - @Autowired - private VersionHistoryService versionHistoryService; - - @PreAuthorize("hasAuthority('ADMIN')") - public VersionRest getCurrentVersion(@Nullable HttpServletRequest request, - Integer versionHistoryId, - @Nullable Pageable optionalPageable, - Projection projection) throws SQLException { - Context context = obtainContext(); - if (Objects.isNull(versionHistoryId) || versionHistoryId < 0) { - throw new DSpaceBadRequestException("Provied id is not correct!"); - } - VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); - if (Objects.isNull(versionHistory)) { - throw new ResourceNotFoundException("No such versio found"); - } - Version currentVersion = versionHistoryService.getLatestVersion(context, versionHistory); - if (Objects.isNull(currentVersion)) { - throw new ResourceNotFoundException("No such version for versionhistory with id:" + versionHistoryId); - } - return converter.toRest(currentVersion, projection); - } - -} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLastVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java similarity index 83% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLastVersionLinkRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java index 3ee62367d5..d9a3b858d1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryLastVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java @@ -31,18 +31,18 @@ import org.springframework.stereotype.Component; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.LAST_VERSION) -public class VersionHistoryLastVersionLinkRepository extends AbstractDSpaceRestRepository +@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.DRAFT_VERSION) +public class VersionHistoryDraftVersionLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired private VersionHistoryService versionHistoryService; @PreAuthorize("hasAuthority('ADMIN')") - public VersionRest getLastVersion(@Nullable HttpServletRequest request, - Integer versionHistoryId, - @Nullable Pageable optionalPageable, - Projection projection) throws SQLException { + public VersionRest getDraftVersion(@Nullable HttpServletRequest request, + Integer versionHistoryId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException { Context context = obtainContext(); if (Objects.isNull(versionHistoryId) || versionHistoryId < 0) { throw new DSpaceBadRequestException("Provied id is not correct!"); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryOldestVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryOldestVersionLinkRepository.java deleted file mode 100644 index 7b4277be25..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryOldestVersionLinkRepository.java +++ /dev/null @@ -1,61 +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.Objects; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; - -import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.model.VersionHistoryRest; -import org.dspace.app.rest.model.VersionRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.core.Context; -import org.dspace.versioning.Version; -import org.dspace.versioning.VersionHistory; -import org.dspace.versioning.service.VersionHistoryService; -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; - -/** - * This is the Repository that takes care of the retrieval of the oldest Version object - * for a given {@link VersionHistory} - * - * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) - */ -@Component(VersionHistoryRest.CATEGORY + "." + VersionHistoryRest.NAME + "." + VersionHistoryRest.OLDEST_VERSION) -public class VersionHistoryOldestVersionLinkRepository extends AbstractDSpaceRestRepository - implements LinkRestRepository { - - @Autowired - private VersionHistoryService versionHistoryService; - - @PreAuthorize("hasAuthority('ADMIN')") - public VersionRest getOldestVersion(@Nullable HttpServletRequest request, - Integer versionHistoryId, - @Nullable Pageable optionalPageable, - Projection projection) throws SQLException { - Context context = obtainContext(); - if (Objects.isNull(versionHistoryId) || versionHistoryId < 0) { - throw new DSpaceBadRequestException("Provied id is not correct!"); - } - VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); - if (Objects.isNull(versionHistory)) { - throw new ResourceNotFoundException("No such versio found"); - } - Version oldestVersion = versionHistoryService.getFirstVersion(context, versionHistory); - if (Objects.isNull(oldestVersion)) { - throw new ResourceNotFoundException("No such version for versionhistory with id:" + versionHistoryId); - } - return converter.toRest(oldestVersion, projection); - } - -} \ No newline at end of file From 527f7e3658cb232416e6ba0c45549efb7f5e6b53 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 8 Sep 2021 18:11:22 +0200 Subject: [PATCH 0227/1254] removed deprecated tests of VersionHistory end point --- .../rest/VersionHistoryRestRepositoryIT.java | 458 +----------------- 1 file changed, 9 insertions(+), 449 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java index 61af01a011..1e922212ab 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java @@ -25,18 +25,14 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.VersionBuilder; -import org.dspace.builder.WorkflowItemBuilder; -import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; -import org.dspace.content.WorkspaceItem; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; -import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -49,7 +45,6 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegrationTest { - VersionHistory versionHistory; Item item; Version version; @@ -96,6 +91,14 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio } + @Test + public void findOneAdminTest() throws Exception { + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + versionHistory.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(VersionHistoryMatcher.matchEntry(versionHistory)))); + + } @Test public void findOneForbiddenTest() throws Exception { @@ -164,449 +167,6 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(version)))) .andExpect(jsonPath("$._embedded.versions", Matchers.not(contains(VersionMatcher.matchEntry(secondVersion))))); - } - @Test - public void findOldestVersionTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - Version v7 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7) - .build(); - VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98) - .build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v7)))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v7.getID()) - ))) - .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v7.getID() + "/versionhistory" - )))) - .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v7.getID() + "/item") - ))); - } - - @Test - public void findOldestVersionNotFoundTest() throws Exception { - context.turnOffAuthorisationSystem(); - VersionHistory versionHistory = versionHistoryService.create(context); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) - .andExpect(status().isNotFound()); - } - - @Test - public void findOldestVersionForbiddenTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7).build(); - VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98).build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String epersonToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonToken).perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) - .andExpect(status().isForbidden()); - } - - @Test - public void findOldestVersionUnauthorizedTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7).build(); - VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98).build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - getClient().perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) - .andExpect(status().isUnauthorized()); - } - - @Test - public void findOldestVersionNTest() throws Exception { - Integer id = Integer.MAX_VALUE; - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/oldestversion")) - .andExpect(status().isNotFound()); - } - - @Test - public void findOldestVersionBadRequestTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + null + "/oldestversion")) - .andExpect(status().isBadRequest()); - } - - @Test - public void findOldestVersionWithNegativVersionHistoryIdTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + -1 + "/oldestversion")) - .andExpect(status().isBadRequest()); - } - - @Test - public void findCurrentVersionTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7) - .build(); - Version v98 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98) - .build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v98)))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v98.getID()) - ))) - .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v98.getID() + "/versionhistory" - )))) - .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v98.getID() + "/item") - ))); - } - - @Test - public void findCurrentVersionNotFoundTest() throws Exception { - context.turnOffAuthorisationSystem(); - VersionHistory versionHistory = versionHistoryService.create(context); - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) - .andExpect(status().isNotFound()); - } - - @Test - public void findCurrentVersionForbiddenTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7).build(); - VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98).build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String epersonToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonToken).perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) - .andExpect(status().isForbidden()); - } - - @Test - public void findCurrentVersionUnauthorizedTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 7).build(); - VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 98).build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - getClient().perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) - .andExpect(status().isUnauthorized()); - } - - @Test - public void findCurrentVersionWithVersionHistoryNotFoundTest() throws Exception { - Integer id = Integer.MAX_VALUE; - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/currentversion")) - .andExpect(status().isNotFound()); - } - - @Test - public void findCurrentVersionBadRequestTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + null + "/currentversion")) - .andExpect(status().isBadRequest()); - } - - @Test - public void findCurrentVersionWithNegativVersionHistoryIdTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + -1 + "/currentversion")) - .andExpect(status().isBadRequest()); - } - - @Test - public void findLastVersionWorkspaceItemTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col) - .withTitle("Workspace Item 1") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 6).build(); - Version v10 = VersionBuilder.createVersionWithVersionHistory(context, witem.getItem(), - "test", versionHistory, 10).build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/lastversion")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v10)))); - } - - @Test - public void findLastVersionItemTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - Version v6 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 6).build(); - VersionBuilder.createVersionWithVersionHistory(context, item2, "test", versionHistory, 3).build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/lastversion")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v6)))); - } - - @Test - public void findLastVersionWorkflowItemTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); - - Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - XmlWorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, col) - .withTitle("Workflow Item") - .withIssueDate("2021-05-21") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 6).build(); - Version v10 = VersionBuilder.createVersionWithVersionHistory(context, workflowItem.getItem(), - "test", versionHistory, 10).build(); - - context.restoreAuthSystemState(); - - Integer id = versionHistory.getID(); - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/lastversion")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v10)))); - } - - @Test - public void findLastVersionNotFoundTest() throws Exception { - Integer id = Integer.MAX_VALUE; - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + id + "/lastversion")) - .andExpect(status().isNotFound()); - } - - @Test - public void findLastVersionBadRequestTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + null + "/lastversion")) - .andExpect(status().isBadRequest()); - } - - @Test - public void findLastVersionWithNegativVersionHistoryIdTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/versioning/versionhistories/" + -1 + "/lastversion")) - .andExpect(status().isBadRequest()); - } -} - +} \ No newline at end of file From 35e280019fbb91be697287d757b5b321d11f0485 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 8 Sep 2021 13:35:46 -0400 Subject: [PATCH 0228/1254] Collections.EMPTY_LIST is immutable, possibly causing addBitstream to throw exception. --- dspace-api/src/main/java/org/dspace/scripts/Process.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2f5ab10f3e..b15fd0c84c 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -7,7 +7,7 @@ */ package org.dspace.scripts; -import java.util.Collections; +import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.Column; @@ -181,7 +181,7 @@ public class Process implements ReloadableEntity { */ public List getBitstreams() { if (bitstreams == null) { - bitstreams = Collections.EMPTY_LIST; + bitstreams = new ArrayList<>(); } return bitstreams; } From 20816f945e228a2f66c87a0dc90ef47afc5eaabd Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 8 Sep 2021 13:40:53 -0400 Subject: [PATCH 0229/1254] Log and continue instead of throwing exception when caller stack is empty. --- .../src/main/java/org/dspace/core/Context.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 9f123b2021..45f5a50df1 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -320,12 +320,19 @@ public class Context implements AutoCloseable { StackTraceElement[] stackTrace = currThread.getStackTrace(); String caller = stackTrace[stackTrace.length - 1].getClassName(); - String previousCaller = (String) authStateClassCallHistory.pop(); + String previousCaller; + try { + previousCaller = (String) authStateClassCallHistory.pop(); + } catch (NoSuchElementException ex) { + previousCaller = "none"; + log.warn(LogManager.getHeader(this, "restore_auth_sys_state", + "no previous caller info available: {}"), + ex::getLocalizedMessage); + } // if previousCaller is not the current caller *only* log a warning if (!previousCaller.equals(caller)) { - log.warn(LogManager - .getHeader( + log.warn(LogManager.getHeader( this, "restore_auth_sys_state", "Class: " From 948014646af8716a8104b0e3960f65f183e52675 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 8 Sep 2021 15:40:44 -0400 Subject: [PATCH 0230/1254] Mention bitstream ID in exception for better debugging. (3061) --- .../main/java/org/dspace/content/BitstreamServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index 98760a43fe..a4d404a811 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -45,7 +45,8 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(BitstreamServiceImpl.class); + private static final Logger log + = org.apache.logging.log4j.LogManager.getLogger(); @Autowired(required = true) @@ -350,7 +351,8 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp public void expunge(Context context, Bitstream bitstream) throws SQLException, AuthorizeException { authorizeService.authorizeAction(context, bitstream, Constants.DELETE); if (!bitstream.isDeleted()) { - throw new IllegalStateException("Bitstream must be deleted before it can be removed from the database"); + throw new IllegalStateException("Bitstream " + bitstream.getID().toString() + + " must be deleted before it can be removed from the database."); } bitstreamDAO.delete(context, bitstream); } From 806572c86d747e654ff23c3c9d17d292d37a7293 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Thu, 9 Sep 2021 11:13:18 -0500 Subject: [PATCH 0231/1254] rename identifier-service-test.xml to identifier-service.xml --- .../api/{identifier-service-test.xml => identifier-service.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dspace-api/src/test/data/dspaceFolder/config/spring/api/{identifier-service-test.xml => identifier-service.xml} (100%) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service-test.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml similarity index 100% rename from dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service-test.xml rename to dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml From 2000cdd56b011d776a2cf8f3c3e618da55ef98fa Mon Sep 17 00:00:00 2001 From: nwoodward Date: Thu, 9 Sep 2021 12:40:32 -0500 Subject: [PATCH 0232/1254] Change maxwait default to 10 seconds --- dspace/config/dspace.cfg | 4 ++-- dspace/config/local.cfg.EXAMPLE | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a042ecb1e4..1bd86b16f2 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -83,8 +83,8 @@ db.schema = public db.maxconnections = 30 # Maximum time to wait before giving up if all connections in pool are busy (milliseconds), (-1 = unlimited) -# (default = 5000ms or 5 seconds) -db.maxwait = 5000 +# (default = 10000ms or 10 seconds) +db.maxwait = 10000 # Minimum number of idle connections in pool # (default = 0) diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index e724da0268..7bd79257d9 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -95,8 +95,8 @@ db.schema = public #db.maxconnections = 30 # Maximum time to wait before giving up if all connections in pool are busy (milliseconds) -# (default = 5000ms or 5 seconds) -#db.maxwait = 5000 +# (default = 10000ms or 10 seconds) +#db.maxwait = 10000 # Maximum number of idle connections in pool (-1 = unlimited) # (default = 10) From f843cfd26aba6c4f80a36c726528bcf68f7a61e7 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 10 Sep 2021 12:49:51 +0200 Subject: [PATCH 0233/1254] refactored VersionBuilder --- .../org/dspace/builder/VersionBuilder.java | 39 +- .../dspace/app/rest/ItemRestRepositoryIT.java | 96 ++--- .../rest/VersionHistoryRestRepositoryIT.java | 407 ++++++++++++++++-- .../app/rest/VersionRestRepositoryIT.java | 299 ++++++++----- 4 files changed, 623 insertions(+), 218 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java b/dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java index 4313c585e1..7295f2196f 100644 --- a/dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java @@ -6,8 +6,8 @@ * http://www.dspace.org/license/ */ package org.dspace.builder; +import java.io.IOException; import java.sql.SQLException; -import java.util.Date; import java.util.Objects; import org.apache.commons.lang.StringUtils; @@ -15,8 +15,8 @@ import org.apache.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.dspace.versioning.Version; -import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersioningService; /** @@ -36,28 +36,16 @@ public class VersionBuilder extends AbstractBuilder public static VersionBuilder createVersion(Context context, Item item, String summary) { VersionBuilder builder = new VersionBuilder(context); - return builder.create(context, item, summary, null, 0); + return builder.create(context, item, summary); } - public static VersionBuilder createVersionWithVersionHistory(Context context, - Item item, String summary, VersionHistory versionHistory, int versionNumber) { - VersionBuilder builder = new VersionBuilder(context); - return builder.create(context, item, summary, versionHistory, versionNumber); - } - - private VersionBuilder create(Context context, Item item, String summary, - VersionHistory versionHistory, int versionNumber) { + private VersionBuilder create(Context context, Item item, String summary) { try { this.context = context; - if (Objects.nonNull(versionHistory)) { - this.version = getService().createNewVersion(context, versionHistory, item, summary, - new Date(), versionNumber); + if (StringUtils.isNotBlank(summary)) { + this.version = getService().createNewVersion(context, item, summary); } else { - if (StringUtils.isNotBlank(summary)) { - this.version = getService().createNewVersion(context, item, summary); - } else { - this.version = getService().createNewVersion(context, item); - } + this.version = getService().createNewVersion(context, item); } } catch (Exception e) { log.error("Error in VersionBuilder.create(..), error: ", e); @@ -106,4 +94,17 @@ public class VersionBuilder extends AbstractBuilder indexingService.commit(); } + public static void delete(Integer id) + throws SQLException, IOException, SearchServiceException { + try (Context context = new Context()) { + context.turnOffAuthorisationSystem(); + Version version = versioningService.getVersion(context, id); + if (version != null) { + versioningService.delete(context, version); + } + context.complete(); + } + indexingService.commit(); + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 8dd598227d..f6549dfa3c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -79,8 +79,6 @@ import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.versioning.Version; -import org.dspace.versioning.VersionHistory; -import org.dspace.versioning.service.VersionHistoryService; import org.dspace.workflow.WorkflowItem; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -93,9 +91,6 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private CollectionService collectionService; - @Autowired - private VersionHistoryService versionHistoryService; - private Item publication1; private Item author1; private Item author2; @@ -4090,7 +4085,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { } @Test - public void versionHistoryForItemTest() throws Exception { + public void finadVersionForItemTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -4098,7 +4093,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); + .withName("Collection test") + .build(); Item item = ItemBuilder.createItem(context, col) .withTitle("Public test item") @@ -4107,38 +4103,24 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - Item item2 = ItemBuilder.createItem(context, col) - .withTitle("Public test item 2") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); - - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10) - .build(); - Version v98 = VersionBuilder.createVersionWithVersionHistory(context, item2, "test 2", versionHistory, 11) - .build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/core/items/" + item2.getID() + "/versionhistory")) + getClient(adminToken).perform(get("/api/core/items/" + item.getID() + "/version")) .andExpect(content().contentType(contentType)) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", is(versionHistory.getID())), - hasJsonPath("$.type", is("versionhistory")) - ))) - .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versionhistories/" + versionHistory.getID()) - ))) - .andExpect(jsonPath("$._links.versions.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versionhistories/" + versionHistory.getID() + "/versions") - ))); + hasJsonPath("$.version", is(1)), + hasJsonPath("$.summary", emptyOrNullString()), + hasJsonPath("$.submitterName", is("first last")), + hasJsonPath("$.type", is("version")) + ))); } @Test - public void versionHistoryForItemNotFoundTest() throws Exception { + public void findVersionOfItemNoContentTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -4146,24 +4128,25 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); + .withName("Collection test") + .build(); Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); context.restoreAuthSystemState(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/core/items/" + item.getID() + "/versionhistory")) + getClient(adminToken).perform(get("/api/core/items/" + item.getID() + "/version")) .andExpect(status().isNoContent()); } @Test - public void versionHistoryForItemUnauthorizedTest() throws Exception { + public void findVersionItemUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -4174,24 +4157,22 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withName("Collection test").build(); Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10) - .build(); + VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/core/items/" + item.getID() + "/versionhistory")) + getClient().perform(get("/api/core/items/" + item.getID() + "/version")) .andExpect(status().isUnauthorized()); } @Test - public void versionHistoryForItemForbiddenTest() throws Exception { + public void findVersionForItemForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -4199,23 +4180,22 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); + .withName("Collection test") + .build(); Item item = ItemBuilder.createItem(context, col) - .withTitle("Public test item") - .withIssueDate("2021-04-27") - .withAuthor("Doe, John") - .withSubject("ExtraEntry") - .build(); + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10) - .build(); + VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); String epersonToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/versionhistory")) - .andExpect(status().isForbidden()); + getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/version")) + .andExpect(status().isForbidden()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java index 1e922212ab..320583a281 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java @@ -7,18 +7,22 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; 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.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.sql.SQLException; -import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; import org.dspace.app.rest.matcher.VersionHistoryMatcher; import org.dspace.app.rest.matcher.VersionMatcher; +import org.dspace.app.rest.matcher.WorkspaceItemMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; @@ -28,6 +32,8 @@ import org.dspace.builder.VersionBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; @@ -35,6 +41,7 @@ import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; import org.hamcrest.Matchers; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -47,7 +54,6 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio VersionHistory versionHistory; Item item; - Version version; @Autowired private ConfigurationService configurationService; @@ -58,6 +64,9 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio @Autowired private VersioningService versioningService; + @Autowired + private WorkspaceItemService workspaceItemService; + @Before public void setup() throws SQLException, AuthorizeException { context.turnOffAuthorisationSystem(); @@ -79,35 +88,64 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio .withAuthor("Smith, Donald").withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); - version = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 0).build(); context.restoreAuthSystemState(); } @Test - public void findOneTest() throws Exception { + public void findOnePublicVersionHistoryTest() throws Exception { getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$", is(VersionHistoryMatcher.matchEntry(versionHistory)))); - + .andExpect(jsonPath("$", is(VersionHistoryMatcher.matchEntry(versionHistory)))) + .andExpect(jsonPath("$._links.versions.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versionhistories/" + versionHistory.getID() + "/versions")))) + .andExpect(jsonPath("$._links.draftVersion.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versionhistories/" + versionHistory.getID() + "/draftVersion")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versionhistories/" + versionHistory.getID())))); } @Test - public void findOneAdminTest() throws Exception { + public void findOnePrivateVersionHistoryByAdminTest() throws Exception { + configurationService.setProperty("versioning.item.history.view.admin", true); + + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + context.turnOffAuthorisationSystem(); + String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(get("/api/versioning/versionhistories/" + versionHistory.getID())) .andExpect(status().isOk()) + .andExpect(jsonPath("$.draftVersion", Matchers.is(true))) .andExpect(jsonPath("$", is(VersionHistoryMatcher.matchEntry(versionHistory)))); + configurationService.setProperty("versioning.item.history.view.admin", false); + } @Test public void findOneForbiddenTest() throws Exception { - configurationService.setProperty("versioning.item.history.view.admin", true); String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/versioning/versionhistories/" + versionHistory.getID())) .andExpect(status().isForbidden()); + configurationService.setProperty("versioning.item.history.view.admin", false); } @@ -127,46 +165,337 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio } @Test - public void findVersionsOfVersionHistoryTest() throws Exception { - Version version = versionHistoryService.getFirstVersion(context, versionHistory); - getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(version)))); + public void findVersionsOfVersionHistoryAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + Version version2 = versioningService.getVersion(context, item); + context.turnOffAuthorisationSystem(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", containsInAnyOrder( + VersionMatcher.matchEntry(version), + VersionMatcher.matchEntry(version2) + ))); context.turnOffAuthorisationSystem(); - Version secondVersion = versioningService - .createNewVersion(context, versionHistory, item, "test", new Date(), 0); + Version version3 = VersionBuilder.createVersion(context, item, "test3").build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", containsInAnyOrder(VersionMatcher.matchEntry(version), - VersionMatcher - .matchEntry(secondVersion)))); - + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", containsInAnyOrder( + VersionMatcher.matchEntry(version), + VersionMatcher.matchEntry(version2), + VersionMatcher.matchEntry(version3) + ))); } + + @Test + public void findVersionsOfVersionHistoryUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + context.turnOffAuthorisationSystem(); + + getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findVersionsOfVersionHistoryLoggedUserTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + Version version2 = versioningService.getVersion(context, item); + context.turnOffAuthorisationSystem(); + + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", containsInAnyOrder( + VersionMatcher.matchEntry(version), + VersionMatcher.matchEntry(version2) + ))); + } + @Test public void findVersionsOfVersionHistoryPaginationTest() throws Exception { - context.turnOffAuthorisationSystem(); - Version version = versionHistoryService.getFirstVersion(context, versionHistory); - Version secondVersion = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 3) - .build(); - context.restoreAuthSystemState(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); - getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions") - .param("size", "1")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(secondVersion)))) - .andExpect(jsonPath("$._embedded.versions", - Matchers.not(contains(VersionMatcher.matchEntry(version))))); - getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions") - .param("size", "1") - .param("page", "1")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(version)))) - .andExpect(jsonPath("$._embedded.versions", - Matchers.not(contains(VersionMatcher.matchEntry(secondVersion))))); + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + Version v1 = versioningService.getVersion(context, item); + Version v3 = VersionBuilder.createVersion(context, item, "test3").build(); + context.turnOffAuthorisationSystem(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions") + .param("size", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(v3)))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions") + .param("size", "1") + .param("page", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(v2)))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions") + .param("size", "1") + .param("page", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(v1)))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + } + + @Test + public void findWorkspaceItemOfDraftVersionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory vh = versionHistoryService.findByItem(context, item); + WorkspaceItem witem = workspaceItemService.findByItem(context, v2.getItem()); + context.turnOffAuthorisationSystem(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + vh.getID() + "/draftVersion")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.is(WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject(witem, + "Public test item", "2021-04-27", "ExtraEntry")))); + } + + @Test + public void findWorkspaceItemOfDraftVersionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory vh = versionHistoryService.findByItem(context, item); + context.turnOffAuthorisationSystem(); + + getClient().perform(get("/api/versioning/versionhistories/" + vh.getID() + "/draftVersion")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findWorkspaceItemOfDraftVersionLoggedUserTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory vh = versionHistoryService.findByItem(context, item); + WorkspaceItem witem = workspaceItemService.findByItem(context, v2.getItem()); + context.turnOffAuthorisationSystem(); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/versioning/versionhistories/" + vh.getID() + "/draftVersion")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.is(WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject(witem, + "Public test item", "2021-04-27", "ExtraEntry")))); + } + + @Test + @Ignore + public void deleteVersionXXXXTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .withSubmitterGroup(admin) + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-03-20") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + Item lastVersionItem = v2.getItem(); + Version v1 = versioningService.getVersion(context, item); + + context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); + String adminToken = getAuthToken(admin.getEmail(), password); + Integer versionID = v2.getID(); + Item versionItem = v2.getItem(); + + // item that linked last version is not archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + // retrieve the workspace item + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/item") + .param("uuid", String.valueOf(lastVersionItem.getID()))) + .andExpect(status().isOk()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submit the workspaceitem to complete the deposit + getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + idRef.get()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // now the item is archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))); + + getClient(adminToken).perform(get("/api/versioning/versions/" + versionID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v2)))) + .andExpect(jsonPath("$._links.versionhistory.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v2.getID() + "/versionhistory")))) + .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v2.getID() + "/item")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( + "api/versioning/versions/" + v2.getID())))); + + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions") + .param("size", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(v2)))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // To delete a version you need to delete the item linked to it. + getClient(adminToken).perform(delete("/api/core/items/" + versionItem.getID())) + .andExpect(status().is(204)); + + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions") + .param("size", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.versions", contains(VersionMatcher.matchEntry(v1)))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(1))); } } \ No newline at end of file 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 1899390383..299e6c5279 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 @@ -6,6 +6,7 @@ * 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.emptyString; import static org.hamcrest.Matchers.is; @@ -20,6 +21,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.VersionMatcher; @@ -80,6 +82,8 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { @Before public void setup() throws SQLException, AuthorizeException { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); context.turnOffAuthorisationSystem(); //** GIVEN ** @@ -255,18 +259,25 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).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("$.submitterName", is("first (admin) last (admin)")), - hasJsonPath("$.type", is("version")) - ))); + + try { + getClient(adminToken).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("$.submitterName", is("first (admin) last (admin)")), + hasJsonPath("$.type", is("version")) + ))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + VersionBuilder.delete(idRef.get()); + } } @Test @@ -307,7 +318,9 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection test").build(); + .withName("Collection test") + .withSubmitterGroup(admin) + .build(); Item item = ItemBuilder.createItem(context, col) .withTitle("Public test item") @@ -316,23 +329,52 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v10 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 10).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); + Item lastVersionItem = v2.getItem(); context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(post("/api/versioning/versions") - .param("summary", "test summary.") - .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) - .content("/api/core/items/" + v10.getItem().getID())) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(11)), - hasJsonPath("$.summary", is("test summary.")), - hasJsonPath("$.submitterName", is("first (admin) last (admin)")), - hasJsonPath("$.type", is("version")) - ))); + + // item that linked last version is not archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + // retrieve the workspace item + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/item") + .param("uuid", String.valueOf(lastVersionItem.getID()))) + .andExpect(status().isOk()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submit the workspaceitem to complete the deposit + getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + idRef.get()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // now the item is archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))); + + try { + getClient(adminToken).perform(post("/api/versioning/versions") + .param("summary", "test summary.") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + v2.getItem().getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(3)), + hasJsonPath("$.summary", is("test summary.")), + hasJsonPath("$.submitterName", is("first (admin) last (admin)")), + hasJsonPath("$.type", is("version")) + ))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + VersionBuilder.delete(idRef.get()); + } } @Test @@ -354,23 +396,63 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 2).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); + Item lastVersionItem = v2.getItem(); context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); String adminToken = getAuthToken(admin.getEmail(), password); + + // the first version item is archived + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))); + + // item that linked last version is not archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + // if there is item not archived, we can not create new version getClient(adminToken).perform(post("/api/versioning/versions") .param("summary", "check first version") .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) .content("/api/core/items/" + item.getID())) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(3)), - hasJsonPath("$.summary", is("check first version")), - hasJsonPath("$.submitterName", is("first (admin) last (admin)")), - hasJsonPath("$.type", is("version")) - ))); + .andExpect(status().isUnprocessableEntity()); + + // retrieve the workspace item + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/item") + .param("uuid", String.valueOf(lastVersionItem.getID()))) + .andExpect(status().isOk()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submit the workspaceitem to complete the deposit + getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + idRef.get()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // now the item is archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))); + + try { + getClient(adminToken).perform(post("/api/versioning/versions") + .param("summary", "check first version") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + item.getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(3)), + hasJsonPath("$.summary", is("check first version")), + hasJsonPath("$.submitterName", is("first (admin) last (admin)")), + hasJsonPath("$.type", is("version")) + ))); + } finally { + VersionBuilder.delete(idRef.get()); + } } @Test @@ -500,18 +582,23 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); String epersonToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonToken).perform(post("/api/versioning/versions") - .param("summary", "test summary!") - .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) - .content("/api/core/items/" + itemA.getID())) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(2)), - hasJsonPath("$.summary", is("test summary!")), - hasJsonPath("$.type", is("version")) - ))); - + try { + getClient(epersonToken).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + itemA.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"))); + } finally { + VersionBuilder.delete(idRef.get()); + } configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); } @@ -565,8 +652,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) + Version v2 = VersionBuilder.createVersion(context, item, "test") .build(); context.restoreAuthSystemState(); @@ -578,21 +664,21 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(adminToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.version", is(v2.getVersionNumber())), hasJsonPath("$.summary", is(newSummary)), hasJsonPath("$.submitterName", is("first last")), hasJsonPath("$.type", is("version")) ))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID()) + "api/versioning/versions/" + v2.getID()) ))) .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID() + "/item") + "api/versioning/versions/" + v2.getID() + "/item") ))); } @@ -615,9 +701,8 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) - .build(); + Version v2 = VersionBuilder.createVersion(context, item, "test") + .build(); context.restoreAuthSystemState(); @@ -627,21 +712,21 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(adminToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.version", is(v2.getVersionNumber())), hasJsonPath("$.summary", emptyString()), hasJsonPath("$.submitterName", is("first last")), hasJsonPath("$.type", is("version")) ))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID()) + "api/versioning/versions/" + v2.getID()) ))) .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID() + "/item") + "api/versioning/versions/" + v2.getID() + "/item") ))); } @@ -664,9 +749,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "", versionHistory, 77) - .build(); + Version v2 = VersionBuilder.createVersion(context, item, "").build(); context.restoreAuthSystemState(); @@ -677,21 +760,21 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(adminToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.version", is(v2.getVersionNumber())), hasJsonPath("$.summary", is(summary)), hasJsonPath("$.submitterName", is("first last")), hasJsonPath("$.type", is("version")) ))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID()) + "api/versioning/versions/" + v2.getID()) ))) .andExpect(jsonPath("$._links.item.href", Matchers.allOf(Matchers.containsString( - "api/versioning/versions/" + v77.getID() + "/item") + "api/versioning/versions/" + v2.getID() + "/item") ))); } @@ -728,9 +811,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) - .build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); @@ -741,7 +822,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(adminToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isBadRequest()); @@ -765,9 +846,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77) - .build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); @@ -778,7 +857,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(adminToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isUnprocessableEntity()); @@ -802,8 +881,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); @@ -814,7 +892,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(adminToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isUnprocessableEntity()); @@ -839,8 +917,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); @@ -850,7 +927,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { ops.add(replaceOperation); String patchBody = getPatchContent(ops); - getClient().perform(patch("/api/versioning/versions/" + v77.getID()) + getClient().perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isUnauthorized()); @@ -874,8 +951,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); @@ -884,7 +960,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { ops.add(replaceOperation); String patchBody = getPatchContent(ops); - getClient().perform(patch("/api/versioning/versions/" + v77.getID()) + getClient().perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isUnauthorized()); @@ -908,8 +984,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); @@ -919,7 +994,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); String epersonToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(epersonToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isForbidden()); @@ -944,8 +1019,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); @@ -956,12 +1030,12 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String patchBody = getPatchContent(ops); String colAdminToken = getAuthToken(eperson.getEmail(), password); - getClient(colAdminToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(colAdminToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.version", is(v2.getVersionNumber())), hasJsonPath("$.summary", is(newSummary)), hasJsonPath("$.type", is("version")) ))); @@ -1005,8 +1079,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v77 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 77).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); context.restoreAuthSystemState(); @@ -1019,24 +1092,24 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { String adminCommAToken = getAuthToken(adminCommA.getEmail(), password); String adminCommBToken = getAuthToken(adminCommB.getEmail(), password); - getClient(adminCommBToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(adminCommBToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isForbidden()); - getClient(adminCommAToken).perform(get("/api/versioning/versions/" + v77.getID())) + getClient(adminCommAToken).perform(get("/api/versioning/versions/" + v2.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v77)))); + .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v2)))); - getClient(adminCommAToken).perform(patch("/api/versioning/versions/" + v77.getID()) + getClient(adminCommAToken).perform(patch("/api/versioning/versions/" + v2.getID()) .content(patchBody) .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()); - getClient(adminCommAToken).perform(get("/api/versioning/versions/" + v77.getID())) + getClient(adminCommAToken).perform(get("/api/versioning/versions/" + v2.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.version", is(v77.getVersionNumber())), + hasJsonPath("$.version", is(v2.getVersionNumber())), hasJsonPath("$.summary", is(newSummary)), hasJsonPath("$.type", is("version")) ))); @@ -1052,6 +1125,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { Collection col = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection test") + .withSubmitterGroup(admin) .build(); Item item = ItemBuilder.createItem(context, col) @@ -1061,14 +1135,38 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("ExtraEntry") .build(); - VersionHistory versionHistory = versionHistoryService.create(context); - Version v2 = VersionBuilder.createVersionWithVersionHistory(context, item, "test", versionHistory, 2).build(); + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); + VersionHistory versionHistory = versionHistoryService.findByItem(context, item); + Item lastVersionItem = v2.getItem(); context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); String adminToken = getAuthToken(admin.getEmail(), password); Integer versionID = v2.getID(); Item versionItem = v2.getItem(); + // item that linked last version is not archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + // retrieve the workspace item + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/item") + .param("uuid", String.valueOf(lastVersionItem.getID()))) + .andExpect(status().isOk()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submit the workspaceitem to complete the deposit + getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + idRef.get()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // now the item is archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))); + getClient(adminToken).perform(get("/api/versioning/versions/" + versionID)) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(v2)))) @@ -1082,9 +1180,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { // To delete a version you need to delete the item linked to it. getClient(adminToken).perform(delete("/api/core/items/" + versionItem.getID())) .andExpect(status().is(204)); - - getClient(adminToken).perform(get("/api/versioning/versions/" + versionID)) - .andExpect(status().isNotFound()); } } \ No newline at end of file From bea710aac62403d56ff8b322aee6abe3a8812dc2 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 10 Sep 2021 12:54:14 +0200 Subject: [PATCH 0234/1254] removed itemVersionHistory link --- .../org/dspace/app/rest/model/ItemRest.java | 5 -- .../ItemVersionHistoryLinkRepository.java | 61 ------------------- .../dspace/app/rest/matcher/ItemMatcher.java | 3 +- 3 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java 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 69c3d117b3..5897f73944 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 @@ -44,10 +44,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; @LinkRest( name = ItemRest.THUMBNAIL, method = "getThumbnail" - ), - @LinkRest( - name = ItemRest.VERSION_HISTORY, - method = "getVersionHistory" ) }) public class ItemRest extends DSpaceObjectRest { @@ -62,7 +58,6 @@ public class ItemRest extends DSpaceObjectRest { public static final String VERSION = "version"; public static final String TEMPLATE_ITEM_OF = "templateItemOf"; public static final String THUMBNAIL = "thumbnail"; - public static final String VERSION_HISTORY = "versionhistory"; private boolean inArchive = false; private boolean discoverable = false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java deleted file mode 100644 index 22193a1a3f..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionHistoryLinkRepository.java +++ /dev/null @@ -1,61 +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.Objects; -import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; - -import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.VersionHistoryRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.content.Item; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.versioning.VersionHistory; -import org.dspace.versioning.service.VersionHistoryService; -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 "versionhistory" subresource of an individual item. - * - * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) - */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.VERSION_HISTORY) -public class ItemVersionHistoryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { - - @Autowired - private ItemService itemService; - - @Autowired - private VersionHistoryService versionHistoryService; - - @PreAuthorize("hasAuthority('ADMIN')") - public VersionHistoryRest getVersionHistory(@Nullable HttpServletRequest request, - UUID itemId, - @Nullable Pageable optionalPageable, - Projection projection) { - try { - Context context = obtainContext(); - Item item = itemService.find(context, itemId); - if (item == null) { - throw new ResourceNotFoundException("No such item: " + itemId); - } - VersionHistory vh = versionHistoryService.findByItem(context, item); - return Objects.nonNull(vh) ? converter.toRest(vh, projection) : null; - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - -} \ No newline at end of file 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 27934b36f5..389b8bf492 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 @@ -56,8 +56,7 @@ public class ItemMatcher { "version", "relationships[]", "templateItemOf", - "thumbnail", - "versionhistory" + "thumbnail" ); } From 3e7892df887247f3940f4f10ade049e006d3313f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 10 Sep 2021 13:10:51 +0200 Subject: [PATCH 0235/1254] refactored version history end point --- .../org/dspace/versioning/VersionHistory.java | 12 ++- .../converter/VersionHistoryConverter.java | 68 +++++++++++++ ...sionHistoryDraftVersionLinkRepository.java | 36 +++++-- .../repository/VersionsLinkRepository.java | 2 + ...orOfAInprogressSubmissionInformations.java | 95 +++++++++++++++++++ 5 files changed, 201 insertions(+), 12 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java index 1acacc7838..af49dd2e81 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java @@ -20,11 +20,12 @@ import javax.persistence.OrderBy; import javax.persistence.SequenceGenerator; import javax.persistence.Table; -import org.apache.logging.log4j.Logger; +import com.amazonaws.util.CollectionUtils; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; import org.hibernate.proxy.HibernateProxyHelper; + /** * @author Fabio Bolognesi (fabio at atmire dot com) * @author Mark Diggory (markd at atmire dot com) @@ -35,8 +36,6 @@ import org.hibernate.proxy.HibernateProxyHelper; @Table(name = "versionhistory") public class VersionHistory implements ReloadableEntity { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(VersionHistory.class); - @Id @Column(name = "versionhistory_id") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "versionhistory_seq") @@ -87,6 +86,13 @@ public class VersionHistory implements ReloadableEntity { this.versions.remove(version); } + public boolean hasDraftVersion() { + if (!CollectionUtils.isNullOrEmpty(versions)) { + return !versions.get(0).getItem().isArchived(); + } + return false; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java index 1b14744132..bd6b027063 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java @@ -7,9 +7,24 @@ */ package org.dspace.app.rest.converter; +import java.sql.SQLException; +import java.util.Objects; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.projection.Projection; +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.ConfigurationService; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** @@ -18,10 +33,30 @@ import org.springframework.stereotype.Component; @Component public class VersionHistoryConverter implements DSpaceConverter { + private static final Logger log = LogManager.getLogger(VersionHistoryConverter.class); + + @Autowired + private RequestService requestService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private VersionHistoryService versionHistoryService; + @Override public VersionHistoryRest convert(VersionHistory modelObject, Projection projection) { + Context context = getContext(); VersionHistoryRest versionHistoryRest = new VersionHistoryRest(); versionHistoryRest.setId(modelObject.getID()); + if (Objects.nonNull(context.getCurrentUser())) { + if (canSeeDraftVersion(context, modelObject)) { + versionHistoryRest.setDraftVersion(modelObject.hasDraftVersion()); + } + } return versionHistoryRest; } @@ -29,4 +64,37 @@ public class VersionHistoryConverter implements DSpaceConverter getModelClass() { return VersionHistory.class; } + + private boolean canSeeDraftVersion(Context context, VersionHistory versionHistory) { + try { + Version version = versionHistoryService.getLatestVersion(context, versionHistory); + if (Objects.nonNull(version)) { + EPerson submitter = version.getItem().getSubmitter(); + boolean isAdmin = authorizeService.isAdmin(context); + boolean canCreateVersion = configurationService + .getBooleanProperty("versioning.submitterCanCreateNewVersion"); + if (!isAdmin && !(canCreateVersion && Objects.equals(submitter, context.getCurrentUser()))) { + return false; + } + return true; + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return false; + } + + /** + * Retrieves the context from the request + * If not request is found, will return null + * @return The context retrieved form the current request or null when no context + */ + private Context getContext() { + Request currentRequest = requestService.getCurrentRequest(); + if (currentRequest != null) { + return ContextUtil.obtainContext(currentRequest.getServletRequest()); + } + return null; + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java index d9a3b858d1..343bb9e9d3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java @@ -12,13 +12,17 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.AInprogressSubmissionRest; import org.dspace.app.rest.model.VersionHistoryRest; -import org.dspace.app.rest.model.VersionRest; 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.versioning.Version; import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; +import org.dspace.workflow.WorkflowItem; +import org.dspace.workflow.WorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -38,11 +42,19 @@ public class VersionHistoryDraftVersionLinkRepository extends AbstractDSpaceRest @Autowired private VersionHistoryService versionHistoryService; - @PreAuthorize("hasAuthority('ADMIN')") - public VersionRest getDraftVersion(@Nullable HttpServletRequest request, - Integer versionHistoryId, - @Nullable Pageable optionalPageable, - Projection projection) throws SQLException { + @Autowired + private WorkspaceItemService workspaceItemService; + + @SuppressWarnings("rawtypes") + @Autowired(required = true) + private WorkflowItemService workflowItemService; + + @PreAuthorize("hasPermission(@extractorOf.getAInprogressSubmissionID(#request, #versionHistoryId), " + + "@extractorOf.getAInprogressSubmissionTarget(#request, #versionHistoryId), 'READ')") + public AInprogressSubmissionRest getDraftVersion(@Nullable HttpServletRequest request, + Integer versionHistoryId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException { Context context = obtainContext(); if (Objects.isNull(versionHistoryId) || versionHistoryId < 0) { throw new DSpaceBadRequestException("Provied id is not correct!"); @@ -52,10 +64,16 @@ public class VersionHistoryDraftVersionLinkRepository extends AbstractDSpaceRest throw new ResourceNotFoundException("No such versio found"); } Version oldestVersion = versionHistoryService.getLatestVersion(context, versionHistory); - if (Objects.isNull(oldestVersion)) { - throw new ResourceNotFoundException("No such version for versionhistory with id:" + versionHistoryId); + + WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); + if (Objects.nonNull(workflowItem)) { + return converter.toRest(workflowItem, projection); } - return converter.toRest(oldestVersion, projection); + if (Objects.nonNull(workspaceItem)) { + return converter.toRest(workspaceItem, projection); + } + return null; } } \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java index 9e29e37bc6..4f317420ed 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java @@ -25,6 +25,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -51,6 +52,7 @@ public class VersionsLinkRepository extends AbstractDSpaceRestRepository * @return The page containing relevant VersionRest objects * @throws SQLException If something goes wrong */ + @PreAuthorize("hasPermission(#versionHistoryId, 'VERSIONHISTORY', 'READ')") public Page getVersions(@Nullable HttpServletRequest request, Integer versionHistoryId, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java new file mode 100644 index 0000000000..a01ce88d80 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java @@ -0,0 +1,95 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; +import java.sql.SQLException; +import java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.dspace.app.rest.model.WorkflowItemRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.versioning.Version; +import org.dspace.versioning.VersionHistory; +import org.dspace.versioning.service.VersionHistoryService; +import org.dspace.workflow.WorkflowItem; +import org.dspace.workflow.WorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(value = "extractorOf") +public class ExtractorOfAInprogressSubmissionInformations { + + @SuppressWarnings("rawtypes") + @Autowired(required = true) + private WorkflowItemService workflowItemService; + + @Autowired + private WorkspaceItemService workspaceItemService; + + @Autowired + private VersionHistoryService versionHistoryService; + + public Integer getAInprogressSubmissionID(@Nullable HttpServletRequest request, Integer versionHistoryId) { + Context context = getContext(request); + if (Objects.nonNull(versionHistoryId)) { + try { + VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); + if (Objects.nonNull(versionHistory)) { + Version oldestVersion = versionHistoryService.getLatestVersion(context, versionHistory); + WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); + if (Objects.nonNull(workspaceItem)) { + return workspaceItem.getID(); + } + if (Objects.nonNull(workflowItem)) { + return workflowItem.getID(); + } + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + return null; + } + + public String getAInprogressSubmissionTarget(@Nullable HttpServletRequest request, Integer versionHistoryId) { + Context context = getContext(request); + if (Objects.nonNull(versionHistoryId)) { + try { + VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); + if (Objects.nonNull(versionHistory)) { + Version oldestVersion = versionHistoryService.getLatestVersion(context, versionHistory); + WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); + if (Objects.nonNull(workspaceItem)) { + return WorkspaceItemRest.NAME; + } + if (Objects.nonNull(workflowItem)) { + return WorkflowItemRest.NAME; + } + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + return StringUtils.EMPTY; + } + + private Context getContext(HttpServletRequest request) { + return Objects.nonNull(request) ? ContextUtil.obtainContext(request) : null; + } + +} \ No newline at end of file From 03ad1684ccf33f9d02008f52cb74ddfe06607d2a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 10 Sep 2021 13:21:29 +0200 Subject: [PATCH 0236/1254] fix test --- .../org/dspace/app/rest/VersionHistoryRestRepositoryIT.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java index 320583a281..7ac1c7fa45 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java @@ -210,6 +210,7 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio @Test public void findVersionsOfVersionHistoryUnauthorizedTest() throws Exception { + configurationService.setProperty("versioning.item.history.view.admin", true); context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -232,6 +233,8 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) .andExpect(status().isUnauthorized()); + + configurationService.setProperty("versioning.item.history.view.admin", true); } @Test From 384d46348aec6c63ac3d4e141a8a91e6944d87e5 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 10 Sep 2021 11:32:58 -0400 Subject: [PATCH 0237/1254] Another batch of Error Prone fixes. (#3061) --- .../app/itemexport/ItemExportServiceImpl.java | 25 +++++---- ...mMarkingAvailabilityBitstreamStrategy.java | 12 ++-- .../dspace/app/itemupdate/ContentsEntry.java | 1 + .../dspace/app/mediafilter/HTMLFilter.java | 7 ++- .../dspace/app/mediafilter/PoiWordFilter.java | 3 +- .../org/dspace/app/sherpa/SHERPAService.java | 1 + .../app/sherpa/v2/SHERPAPermittedVersion.java | 19 ++++--- .../sherpa/v2/SHERPAPublisherResponse.java | 7 ++- .../dspace/app/sherpa/v2/SHERPAResponse.java | 11 ++-- .../app/sitemap/SitemapsOrgGenerator.java | 2 +- .../dspace/app/util/InitializeEntities.java | 13 +---- .../main/java/org/dspace/app/util/Util.java | 14 ++--- .../checker/SimpleReporterServiceImpl.java | 14 ++--- .../java/org/dspace/content/DCPersonName.java | 4 +- .../content/RelationshipServiceImpl.java | 7 ++- .../crosswalk/OREIngestionCrosswalk.java | 7 +-- .../content/dao/impl/CollectionDAOImpl.java | 6 +- .../java/org/dspace/content/logic/Filter.java | 2 +- .../content/logic/condition/Condition.java | 3 +- .../dspace/content/logic/operator/Nor.java | 3 +- .../dspace/content/packager/METSManifest.java | 11 ++-- .../src/main/java/org/dspace/core/Email.java | 4 +- .../indexobject/AbstractIndexableObject.java | 2 +- .../org/dspace/eperson/LoadLastLogin.java | 3 +- .../impl/SHERPAv2JournalDataProvider.java | 3 - .../identifier/DOIIdentifierProvider.java | 40 +++++++------ .../org/dspace/license/LicenseCleanup.java | 12 ++-- .../processor/ExportEventProcessor.java | 10 ++-- .../export/processor/ItemEventProcessor.java | 6 +- .../storage/bitstore/BitStoreMigrate.java | 4 -- .../dspace/storage/rdbms/DatabaseUtils.java | 22 ++++---- dspace-api/src/test/resources/log4j2-test.xml | 56 +++++++++++++++++++ 32 files changed, 199 insertions(+), 135 deletions(-) create mode 100644 dspace-api/src/test/resources/log4j2-test.xml diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java index 7d5e63c127..59ee6d8ba5 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java @@ -16,6 +16,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -129,7 +130,7 @@ public class ItemExportServiceImpl implements ItemExportService { while (i.hasNext()) { if (SUBDIR_LIMIT > 0 && ++counter == SUBDIR_LIMIT) { - subdir = Integer.valueOf(subDirSuffix++).toString(); + subdir = Integer.toString(subDirSuffix++); fullPath = destDirName + File.separatorChar + subdir; counter = 0; @@ -191,7 +192,7 @@ public class ItemExportServiceImpl implements ItemExportService { */ protected void writeMetadata(Context c, Item i, File destDir, boolean migrate) throws Exception { - Set schemas = new HashSet(); + Set schemas = new HashSet<>(); List dcValues = itemService.getMetadata(i, Item.ANY, Item.ANY, Item.ANY, Item.ANY); for (MetadataValue metadataValue : dcValues) { schemas.add(metadataValue.getMetadataField().getMetadataSchema().getName()); @@ -267,7 +268,7 @@ public class ItemExportServiceImpl implements ItemExportService { + Utils.addEntities(dcv.getValue()) + "\n") .getBytes("UTF-8"); - if ((!migrate) || + if (!migrate || (migrate && !( ("date".equals(metadataField.getElement()) && "issued".equals(qualifier)) || ("date".equals(metadataField.getElement()) && "accessioned".equals(qualifier)) || @@ -292,10 +293,10 @@ public class ItemExportServiceImpl implements ItemExportService { } // When migrating, only keep date.issued if it is different to date.accessioned - if ((migrate) && + if (migrate && (dateIssued != null) && (dateAccessioned != null) && - (!dateIssued.equals(dateAccessioned))) { + !dateIssued.equals(dateAccessioned)) { utf8 = (" " + Utils.addEntities(dateIssued) + "\n") @@ -330,7 +331,7 @@ public class ItemExportServiceImpl implements ItemExportService { File outFile = new File(destDir, filename); if (outFile.createNewFile()) { - PrintWriter out = new PrintWriter(new FileWriter(outFile)); + PrintWriter out = new PrintWriter(new FileWriter(outFile, StandardCharsets.UTF_8)); out.println(i.getHandle()); @@ -360,7 +361,7 @@ public class ItemExportServiceImpl implements ItemExportService { File outFile = new File(destDir, "contents"); if (outFile.createNewFile()) { - PrintWriter out = new PrintWriter(new FileWriter(outFile)); + PrintWriter out = new PrintWriter(new FileWriter(outFile, StandardCharsets.UTF_8)); List bundles = i.getBundles(); @@ -474,7 +475,7 @@ public class ItemExportServiceImpl implements ItemExportService { public void createDownloadableExport(DSpaceObject dso, Context context, boolean migrate) throws Exception { EPerson eperson = context.getCurrentUser(); - ArrayList list = new ArrayList(1); + ArrayList list = new ArrayList<>(1); list.add(dso); processDownloadableExport(list, context, eperson == null ? null : eperson.getEmail(), migrate); @@ -491,7 +492,7 @@ public class ItemExportServiceImpl implements ItemExportService { @Override public void createDownloadableExport(DSpaceObject dso, Context context, String additionalEmail, boolean migrate) throws Exception { - ArrayList list = new ArrayList(1); + ArrayList list = new ArrayList<>(1); list.add(dso); processDownloadableExport(list, context, additionalEmail, migrate); } @@ -652,7 +653,7 @@ public class ItemExportServiceImpl implements ItemExportService { while (iter.hasNext()) { String keyName = iter.next(); List uuids = itemsMap.get(keyName); - List items = new ArrayList(); + List items = new ArrayList<>(); for (UUID uuid : uuids) { items.add(itemService.find(context, uuid)); } @@ -876,7 +877,7 @@ public class ItemExportServiceImpl implements ItemExportService { .getIntProperty("org.dspace.app.itemexport.life.span.hours"); Calendar now = Calendar.getInstance(); now.setTime(new Date()); - now.add(Calendar.HOUR, (-hours)); + now.add(Calendar.HOUR, -hours); File downloadDir = new File(getExportDownloadDirectory(eperson)); if (downloadDir.exists()) { File[] files = downloadDir.listFiles(); @@ -896,7 +897,7 @@ public class ItemExportServiceImpl implements ItemExportService { int hours = configurationService.getIntProperty("org.dspace.app.itemexport.life.span.hours"); Calendar now = Calendar.getInstance(); now.setTime(new Date()); - now.add(Calendar.HOUR, (-hours)); + now.add(Calendar.HOUR, -hours); File downloadDir = new File(configurationService.getProperty("org.dspace.app.itemexport.download.dir")); if (downloadDir.exists()) { // Get a list of all the sub-directories, potentially one for each ePerson. diff --git a/dspace-api/src/main/java/org/dspace/app/itemmarking/ItemMarkingAvailabilityBitstreamStrategy.java b/dspace-api/src/main/java/org/dspace/app/itemmarking/ItemMarkingAvailabilityBitstreamStrategy.java index cd08ad032c..31166add72 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemmarking/ItemMarkingAvailabilityBitstreamStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/itemmarking/ItemMarkingAvailabilityBitstreamStrategy.java @@ -11,6 +11,8 @@ import java.io.UnsupportedEncodingException; import java.sql.SQLException; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.util.Util; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -34,8 +36,9 @@ public class ItemMarkingAvailabilityBitstreamStrategy implements ItemMarkingExtr @Autowired(required = true) protected ItemService itemService; - public ItemMarkingAvailabilityBitstreamStrategy() { + private static final Logger LOG = LogManager.getLogger(); + public ItemMarkingAvailabilityBitstreamStrategy() { } @Override @@ -43,14 +46,14 @@ public class ItemMarkingAvailabilityBitstreamStrategy implements ItemMarkingExtr throws SQLException { List bundles = itemService.getBundles(item, "ORIGINAL"); - if (bundles.size() == 0) { + if (bundles.isEmpty()) { ItemMarkingInfo markInfo = new ItemMarkingInfo(); markInfo.setImageName(nonAvailableImageName); return markInfo; } else { Bundle originalBundle = bundles.iterator().next(); - if (originalBundle.getBitstreams().size() == 0) { + if (originalBundle.getBitstreams().isEmpty()) { ItemMarkingInfo markInfo = new ItemMarkingInfo(); markInfo.setImageName(nonAvailableImageName); @@ -72,8 +75,7 @@ public class ItemMarkingAvailabilityBitstreamStrategy implements ItemMarkingExtr try { bsLink = bsLink + Util.encodeBitstreamName(bitstream.getName(), Constants.DEFAULT_ENCODING); } catch (UnsupportedEncodingException e) { - - e.printStackTrace(); + LOG.warn("DSpace uses an unsupported encoding", e); } signInfo.setLink(bsLink); diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/ContentsEntry.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/ContentsEntry.java index e192b92b89..7bbe4a19e9 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/ContentsEntry.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/ContentsEntry.java @@ -105,6 +105,7 @@ public class ContentsEntry { return new ContentsEntry(arp[0], arp[1], actionId, groupName, arp[3]); } + @Override public String toString() { StringBuilder sb = new StringBuilder(filename); if (bundlename != null) { diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/HTMLFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/HTMLFilter.java index 1b982cb277..5e10f2841d 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/HTMLFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/HTMLFilter.java @@ -9,6 +9,7 @@ package org.dspace.app.mediafilter; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import javax.swing.text.Document; import javax.swing.text.html.HTMLEditorKit; @@ -36,7 +37,7 @@ public class HTMLFilter extends MediaFilter { } /** - * @return String bitstreamformat + * @return String bitstream format */ @Override public String getFormatString() { @@ -73,9 +74,9 @@ public class HTMLFilter extends MediaFilter { String extractedText = doc.getText(0, doc.getLength()); // generate an input stream with the extracted text - byte[] textBytes = extractedText.getBytes(); + byte[] textBytes = extractedText.getBytes(StandardCharsets.UTF_8); ByteArrayInputStream bais = new ByteArrayInputStream(textBytes); - return bais; // will this work? or will the byte array be out of scope? + return bais; } } diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java index 158f52f1f9..8c198c4477 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java @@ -10,6 +10,7 @@ package org.dspace.app.mediafilter; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import org.apache.poi.POITextExtractor; import org.apache.poi.extractor.ExtractorFactory; @@ -66,6 +67,6 @@ public class PoiWordFilter } // return the extracted text as a stream. - return new ByteArrayInputStream(text.getBytes()); + return new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)); } } diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java index 596bd63643..87198fe172 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java @@ -73,6 +73,7 @@ public class SHERPAService { /** * Complete initialization of the Bean. */ + @SuppressWarnings("unused") @PostConstruct private void init() { // Get endoint and API key from configuration diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAPermittedVersion.java b/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAPermittedVersion.java index ec45a29ce7..3a810c8e9e 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAPermittedVersion.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAPermittedVersion.java @@ -13,15 +13,18 @@ import java.util.List; * Plain java representation of a SHERPA Permitted Version object, based on SHERPA API v2 responses. * * In a SHERPA search for journal deposit policies, this data is contained within a publisher policy. - * Each permitted version is for a particular article version (eg. submitted, accepted, published) and contains + * Each permitted version is for a particular article version (e.g. submitted, accepted, published) and contains: * - * A list of general conditions / terms for deposit of this version of work - * A list of allowed locations (eg. institutional repository, personal homepage, non-commercial repository) - * A list of prerequisite conditions for deposit (eg. attribution, linking to published version) - * A list of required licences for the deposited work (eg. CC-BY-NC) - * Embargo requirements, if any + *
      + *
    • A list of general conditions / terms for deposit of this version of work
    • + *
    • A list of allowed locations (e.g. institutional repository, personal homepage, non-commercial repository)
    • + *
    • A list of prerequisite conditions for deposit (e.g. attribution, linking to published version)
    • + *
    • A list of required licenses for the deposited work (e.g. CC-BY-NC)
    • + *
    • Embargo requirements, if any
    • + *
    * - * This class also has some helper data for labels, which can be used with i18n when displaying policy information + * This class also has some helper data for labels, which can be used with i18n + * when displaying policy information. * * @see SHERPAPublisherPolicy */ @@ -44,7 +47,7 @@ public class SHERPAPermittedVersion { // Embargo private SHERPAEmbargo embargo; - protected class SHERPAEmbargo { + protected static class SHERPAEmbargo { String units; int amount; } diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAPublisherResponse.java b/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAPublisherResponse.java index f109b2e677..ac71c6e844 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAPublisherResponse.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAPublisherResponse.java @@ -10,7 +10,8 @@ package org.dspace.app.sherpa.v2; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.LinkedList; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import org.apache.logging.log4j.LogManager; @@ -74,7 +75,7 @@ public class SHERPAPublisherResponse { * @param jsonData - the JSON input stream from the API result response body */ private void parseJSON(InputStream jsonData) throws IOException { - InputStreamReader streamReader = new InputStreamReader(jsonData); + InputStreamReader streamReader = new InputStreamReader(jsonData, StandardCharsets.UTF_8); JSONTokener jsonTokener = new JSONTokener(streamReader); JSONObject httpResponse; try { @@ -86,7 +87,7 @@ public class SHERPAPublisherResponse { // parsing the full journal / policy responses if (items.length() > 0) { metadata = new SHERPASystemMetadata(); - this.publishers = new LinkedList<>(); + this.publishers = new ArrayList<>(); // Iterate search result items for (int itemIndex = 0; itemIndex < items.length(); itemIndex++) { SHERPAPublisher sherpaPublisher = new SHERPAPublisher(); diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAResponse.java b/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAResponse.java index 3134ad013c..a40814bafe 100644 --- a/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAResponse.java +++ b/dspace-api/src/main/java/org/dspace/app/sherpa/v2/SHERPAResponse.java @@ -10,8 +10,8 @@ package org.dspace.app.sherpa.v2; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -78,7 +78,7 @@ public class SHERPAResponse { * @param jsonData - the JSON input stream from the API result response body */ private void parseJSON(InputStream jsonData) throws IOException { - InputStreamReader streamReader = new InputStreamReader(jsonData); + InputStreamReader streamReader = new InputStreamReader(jsonData, StandardCharsets.UTF_8); JSONTokener jsonTokener = new JSONTokener(streamReader); JSONObject httpResponse; try { @@ -90,10 +90,10 @@ public class SHERPAResponse { // - however, we only ever want one result since we're passing an "equals ISSN" query if (items.length() > 0) { metadata = new SHERPASystemMetadata(); - this.journals = new LinkedList<>(); + this.journals = new ArrayList<>(); // Iterate search result items for (int itemIndex = 0; itemIndex < items.length(); itemIndex++) { - List sherpaPublishers = new LinkedList<>(); + List sherpaPublishers = new ArrayList<>(); List policies = new ArrayList<>(); SHERPAPublisher sherpaPublisher = new SHERPAPublisher(); SHERPAJournal sherpaJournal = new SHERPAJournal(); @@ -289,7 +289,7 @@ public class SHERPAResponse { // Is the item in DOAJ? if (item.has("listed_in_doaj")) { - sherpaJournal.setInDOAJ(("yes".equals(item.getString("listed_in_doaj")))); + sherpaJournal.setInDOAJ("yes".equals(item.getString("listed_in_doaj"))); } return sherpaJournal; @@ -403,7 +403,6 @@ public class SHERPAResponse { // published = pdfversion // These strings can be used to construct i18n messages. String articleVersion = "unknown"; - String versionLabel = "Unknown"; // Each 'permitted OA' can actually refer to multiple versions if (permitted.has("article_version")) { diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java b/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java index 3ec4ca8239..53f402d331 100644 --- a/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java +++ b/dspace-api/src/main/java/org/dspace/app/sitemap/SitemapsOrgGenerator.java @@ -86,7 +86,7 @@ public class SitemapsOrgGenerator extends AbstractGenerator { @Override public String getURLText(String url, Date lastMod) { - StringBuffer urlText = new StringBuffer(); + StringBuilder urlText = new StringBuilder(); urlText.append("").append(url).append(""); if (lastMod != null) { diff --git a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java index 5b2413642c..0a072a9819 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java +++ b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java @@ -10,8 +10,6 @@ package org.dspace.app.util; import java.io.File; import java.io.IOException; import java.sql.SQLException; -import java.util.LinkedList; -import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -30,7 +28,6 @@ import org.dspace.content.EntityType; import org.dspace.content.RelationshipType; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.EntityTypeService; -import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.core.Context; import org.w3c.dom.Document; @@ -40,22 +37,20 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** - * This script is used to initialize the database with a set of relationshiptypes that are written + * This script is used to initialize the database with a set of relationship types that are written * in an xml file that is given to this script. - * This XML file needs to have a proper XML structure and needs to define the variables of the RelationshipType object + * This XML file needs to have a proper XML structure and needs to define the variables of the RelationshipType object. */ public class InitializeEntities { private final static Logger log = LogManager.getLogger(); private final RelationshipTypeService relationshipTypeService; - private final RelationshipService relationshipService; private final EntityTypeService entityTypeService; private InitializeEntities() { relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); - relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); } @@ -111,14 +106,12 @@ public class InitializeEntities { try { File fXmlFile = new File(fileLocation); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = null; - dBuilder = dbFactory.newDocumentBuilder(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); Document doc = dBuilder.parse(fXmlFile); doc.getDocumentElement().normalize(); NodeList nList = doc.getElementsByTagName("type"); - List relationshipTypes = new LinkedList<>(); for (int i = 0; i < nList.getLength(); i++) { Node nNode = nList.item(i); diff --git a/dspace-api/src/main/java/org/dspace/app/util/Util.java b/dspace-api/src/main/java/org/dspace/app/util/Util.java index aa04c13be7..f8ef3b1731 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/Util.java +++ b/dspace-api/src/main/java/org/dspace/app/util/Util.java @@ -38,13 +38,12 @@ import org.dspace.core.Utils; * * @author Robert Tansley * @author Mark Diggory - * @version $Revision$ */ public class Util { // cache for source version result private static String sourceVersion = null; - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(Util.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); /** * Default constructor. Must be protected as org.dspace.xmlworkflow.WorkflowUtils extends it @@ -60,7 +59,7 @@ public class Util { * spaces */ public static String nonBreakSpace(String s) { - StringBuffer newString = new StringBuffer(); + StringBuilder newString = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); @@ -99,7 +98,7 @@ public class Util { return ""; } - StringBuffer out = new StringBuffer(); + StringBuilder out = new StringBuilder(); final String[] pctEncoding = {"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0a", "%0b", "%0c", "%0d", @@ -263,7 +262,7 @@ public class Util { return null; } - List return_values = new ArrayList(request_values.length); + List return_values = new ArrayList<>(request_values.length); for (String s : request_values) { try { @@ -402,7 +401,7 @@ public class Util { Item item, List values, String schema, String element, String qualifier, Locale locale) throws SQLException, DCInputsReaderException { - List toReturn = new ArrayList(); + List toReturn = new ArrayList<>(); DCInput myInputs = null; boolean myInputsFound = false; String formFileName = I18nUtil.getInputFormsFileName(locale); @@ -478,8 +477,9 @@ public class Util { } /** - * Split a list in an array of i sub-lists uniformly sized + * Split a list in an array of i sub-lists uniformly sized. * + * @param type of objects in the list. * @param idsList the list to split * @param i the number of sublists to return * diff --git a/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java b/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java index 849cddfb61..26c102e1e7 100644 --- a/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java @@ -77,7 +77,7 @@ public class SimpleReporterServiceImpl implements SimpleReporterService { osw.write(applyDateFormatShort(endDate)); osw.write("\n\n\n"); - if (recentChecksums.size() == 0) { + if (recentChecksums.isEmpty()) { osw.write("\n\n"); osw.write(msg("no-bitstreams-to-delete")); osw.write("\n"); @@ -119,7 +119,7 @@ public class SimpleReporterServiceImpl implements SimpleReporterService { osw.write(applyDateFormatShort(endDate)); osw.write("\n\n\n"); - if (history.size() == 0) { + if (history.isEmpty()) { osw.write("\n\n"); osw.write(msg("no-changed-bitstreams")); osw.write("\n"); @@ -159,7 +159,7 @@ public class SimpleReporterServiceImpl implements SimpleReporterService { osw.write(applyDateFormatShort(endDate)); osw.write("\n\n\n"); - if (history.size() == 0) { + if (history.isEmpty()) { osw.write("\n\n"); osw.write(msg("no-bitstreams-changed")); osw.write("\n"); @@ -201,7 +201,7 @@ public class SimpleReporterServiceImpl implements SimpleReporterService { osw.write(applyDateFormatShort(endDate)); osw.write("\n\n\n"); - if (mostRecentChecksums.size() == 0) { + if (mostRecentChecksums.isEmpty()) { osw.write("\n\n"); osw.write(msg("no-bitstreams-to-no-longer-be-processed")); osw.write("\n"); @@ -233,7 +233,7 @@ public class SimpleReporterServiceImpl implements SimpleReporterService { osw.write(applyDateFormatShort(new Date())); osw.write("\n\n\n"); - if (bitstreams.size() == 0) { + if (bitstreams.isEmpty()) { osw.write("\n\n"); osw.write(msg("no-unchecked-bitstreams")); osw.write("\n"); @@ -257,7 +257,7 @@ public class SimpleReporterServiceImpl implements SimpleReporterService { protected void printHistoryRecords(List mostRecentChecksums, OutputStreamWriter osw) throws IOException { for (MostRecentChecksum mostRecentChecksum : mostRecentChecksums) { - StringBuffer buf = new StringBuffer(1000); + StringBuilder buf = new StringBuilder(1000); buf.append("------------------------------------------------ \n"); buf.append(msg("bitstream-id")).append(" = ").append( mostRecentChecksum.getBitstream().getID()).append("\n"); @@ -292,7 +292,7 @@ public class SimpleReporterServiceImpl implements SimpleReporterService { throws IOException, SQLException { for (Bitstream info : bitstreams) { - StringBuffer buf = new StringBuffer(1000); + StringBuilder buf = new StringBuilder(1000); buf.append("------------------------------------------------ \n"); buf.append(msg("format-id")).append(" = ").append( info.getFormat(context).getID()).append("\n"); diff --git a/dspace-api/src/main/java/org/dspace/content/DCPersonName.java b/dspace-api/src/main/java/org/dspace/content/DCPersonName.java index cdcff55c37..cb9b5346ff 100644 --- a/dspace-api/src/main/java/org/dspace/content/DCPersonName.java +++ b/dspace-api/src/main/java/org/dspace/content/DCPersonName.java @@ -18,7 +18,6 @@ package org.dspace.content; * FIXME: No policy for dealing with "van"/"van der" and "Jr." * * @author Robert Tansley - * @version $Revision$ */ public class DCPersonName { /** @@ -89,8 +88,9 @@ public class DCPersonName { * * @return the name, suitable for putting in the database */ + @Override public String toString() { - StringBuffer out = new StringBuffer(); + StringBuilder out = new StringBuilder(); if (lastName != null) { out.append(lastName); diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index 1b419da816..958b51ff7e 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -8,9 +8,9 @@ package org.dspace.content; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.LinkedList; import java.util.List; import org.apache.commons.collections4.CollectionUtils; @@ -250,7 +250,7 @@ public class RelationshipServiceImpl implements RelationshipService { } List rightRelationships = findByItemAndRelationshipType(context, itemToProcess, relationshipType, isLeft); - if (maxCardinality != null && rightRelationships.size() >= maxCardinality) { + if (rightRelationships.size() >= maxCardinality) { return false; } return true; @@ -266,6 +266,7 @@ public class RelationshipServiceImpl implements RelationshipService { return StringUtils.equals(leftEntityType, entityTypeToProcess.getLabel()); } + @Override public Relationship find(Context context, int id) throws SQLException { Relationship relationship = relationshipDAO.findByID(context, Relationship.class, id); return relationship; @@ -407,7 +408,7 @@ public class RelationshipServiceImpl implements RelationshipService { // Set a limit on the total depth of relationships to traverse during a relationship change int maxDepth = configurationService.getIntProperty("relationship.update.relateditems.maxdepth", 5); // This is the list containing all items which will have changes to their virtual metadata - List itemsToUpdate = new LinkedList<>(); + List itemsToUpdate = new ArrayList<>(); itemsToUpdate.add(relationship.getLeftItem()); itemsToUpdate.add(relationship.getRightItem()); diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java index 6d7c716189..80c424e782 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java @@ -44,17 +44,16 @@ import org.jdom.xpath.XPath; /** * ORE ingestion crosswalk *

    - * Processes an Atom-encoded ORE resource map and attemps to interpret it as a DSpace item + * Processes an Atom-encoded ORE resource map and attempts to interpret it as a DSpace item. * * @author Alexey Maslov - * @version $Revision: 1 $ */ public class OREIngestionCrosswalk implements IngestionCrosswalk { /** * log4j category */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OREDisseminationCrosswalk.class); + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); /* Namespaces */ public static final Namespace ATOM_NS = @@ -149,7 +148,7 @@ public class OREIngestionCrosswalk xpathDesc.addNamespace(RDF_NS); desc = (Element) xpathDesc.selectSingleNode(doc); } catch (JDOMException e) { - e.printStackTrace(); + log.warn("Could not find description for {}", href, e); } if (desc != null && desc.getChild("type", RDF_NS).getAttributeValue("resource", RDF_NS) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index 87c7c3155d..c0ef6ea42f 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -9,7 +9,7 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.AbstractMap; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.persistence.Query; @@ -119,7 +119,7 @@ public class CollectionDAOImpl extends AbstractHibernateDSODAO imple CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Collection.class); Root collectionRoot = criteriaQuery.from(Collection.class); Join join = collectionRoot.join("resourcePolicies"); - List orPredicates = new LinkedList<>(); + List orPredicates = new ArrayList<>(actions.size()); for (Integer action : actions) { orPredicates.add(criteriaBuilder.equal(join.get(ResourcePolicy_.actionId), action)); } @@ -176,7 +176,7 @@ public class CollectionDAOImpl extends AbstractHibernateDSODAO imple Query query = createQuery(context, q); List list = query.getResultList(); - List> returnList = new LinkedList<>(); + List> returnList = new ArrayList<>(list.size()); for (Object[] o : list) { returnList.add(new AbstractMap.SimpleEntry<>((Collection) o[0], (Long) o[1])); } 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 bfacca837b..84e9d6bc08 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 @@ -20,7 +20,6 @@ import org.dspace.core.Context; * logical statement that shouldn't be use as root element. A filter may contain only one substatement. * * @author Kim Shepherd - * @version $Revision$ * @see org.dspace.content.logic.DefaultFilter */ public interface Filter extends LogicalStatement { @@ -31,5 +30,6 @@ public interface Filter extends LogicalStatement { * @return boolean * @throws LogicalStatementException */ + @Override boolean getResult(Context context, Item item) throws LogicalStatementException; } 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 325964efdb..c86509899f 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 @@ -45,9 +45,10 @@ public interface Condition extends LogicalStatement { * Get the result of logical evaluation for an item * @param context DSpace context * @param item Item to evaluate - * @return boolean + * @return result * @throws LogicalStatementException */ + @Override boolean getResult(Context context, Item item) throws LogicalStatementException; public void setItemService(ItemService itemService); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java index d28ac7578d..d312734fbf 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nor.java @@ -15,10 +15,9 @@ import org.dspace.content.logic.LogicalStatementException; import org.dspace.core.Context; /** - * An operator that implements NIR by negating an OR operation + * An operator that implements NOR by negating an OR operation. * * @author Kim Shepherd - * @version $Revision$ */ public class Nor extends AbstractOperator { diff --git a/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java b/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java index ffdb304802..8fb8172aeb 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java @@ -11,6 +11,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; @@ -656,7 +657,7 @@ public class METSManifest { String mimeType = mdWrap.getAttributeValue("MIMETYPE"); if (mimeType != null && mimeType.equalsIgnoreCase("text/xml")) { - byte value[] = Base64.decodeBase64(bin.getText().getBytes()); + byte value[] = Base64.decodeBase64(bin.getText().getBytes(StandardCharsets.UTF_8)); Document mdd = parser.build(new ByteArrayInputStream(value)); List result = new ArrayList<>(1); result.add(mdd.getRootElement()); @@ -724,13 +725,13 @@ public class METSManifest { throw new MetadataValidationException( "Invalid METS Manifest: mdWrap element with neither xmlData nor binData child."); } else { - byte value[] = Base64.decodeBase64(bin.getText().getBytes()); + byte value[] = Base64.decodeBase64(bin.getText().getBytes(StandardCharsets.UTF_8)); return new ByteArrayInputStream(value); } } else { XMLOutputter outputPretty = new XMLOutputter(Format.getPrettyFormat()); return new ByteArrayInputStream( - outputPretty.outputString(xmlData.getChildren()).getBytes()); + outputPretty.outputString(xmlData.getChildren()).getBytes(StandardCharsets.UTF_8)); } } else { mdRef = mdSec.getChild("mdRef", metsNS); @@ -1176,7 +1177,7 @@ public class METSManifest { "Invalid METS Manifest: mdWrap element for streaming crosswalk without binData " + "child."); } else { - byte value[] = Base64.decodeBase64(bin.getText().getBytes()); + byte value[] = Base64.decodeBase64(bin.getText().getBytes(StandardCharsets.UTF_8)); sxwalk.ingest(context, dso, new ByteArrayInputStream(value), mdWrap.getAttributeValue("MIMETYPE")); @@ -1302,6 +1303,6 @@ public class METSManifest { XMLOutputter outputPretty = new XMLOutputter(Format.getPrettyFormat()); return new ByteArrayInputStream( - outputPretty.outputString(mets).getBytes()); + outputPretty.outputString(mets).getBytes(StandardCharsets.UTF_8)); } } 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 803497b650..2e9b058c06 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -571,7 +571,7 @@ public class Email { /** * @author arnaldo */ - public class InputStreamDataSource implements DataSource { + public static class InputStreamDataSource implements DataSource { private final String name; private final String contentType; private final ByteArrayOutputStream baos; @@ -612,7 +612,7 @@ public class Email { * Wrap ConfigurationService to prevent templates from modifying * the configuration. */ - public class UnmodifiableConfigurationService { + public static class UnmodifiableConfigurationService { private final ConfigurationService configurationService; /** diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java index cf3f22ccdf..0fb01f2e5b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/AbstractIndexableObject.java @@ -33,7 +33,7 @@ public abstract class AbstractIndexableObject, PK if (!(obj instanceof IndexableObject)) { return false; } - AbstractIndexableObject other = (AbstractIndexableObject) obj; + IndexableObject other = (IndexableObject) obj; return other.getIndexedObject().equals(getIndexedObject()); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/LoadLastLogin.java b/dspace-api/src/main/java/org/dspace/eperson/LoadLastLogin.java index b6bb99c442..390340affd 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/LoadLastLogin.java +++ b/dspace-api/src/main/java/org/dspace/eperson/LoadLastLogin.java @@ -11,6 +11,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -108,7 +109,7 @@ public class LoadLastLogin { final SimpleDateFormat dateEncoder = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (String logName : args) { - BufferedReader logReader = new BufferedReader(new FileReader(logName)); + BufferedReader logReader = new BufferedReader(new FileReader(logName, StandardCharsets.UTF_8)); while (true) { String line = logReader.readLine(); // End of file? diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java index 42d3cab494..210d8a24a8 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java @@ -15,7 +15,6 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; import org.dspace.app.sherpa.SHERPAService; import org.dspace.app.sherpa.v2.SHERPAJournal; import org.dspace.app.sherpa.v2.SHERPAResponse; @@ -33,8 +32,6 @@ import org.dspace.external.provider.ExternalDataProvider; */ public class SHERPAv2JournalDataProvider implements ExternalDataProvider { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SHERPAv2JournalDataProvider.class); - // Source identifier (configured in spring configuration) private String sourceIdentifier; 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 4dbfcf0eae..da9c3b718a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -158,6 +158,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * 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; } @@ -319,7 +320,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation * @throws IdentifierException * @throws IllegalArgumentException - * @throws SQLException */ @Override public void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter) @@ -367,6 +367,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @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)} * @throws IdentifierException * @throws IllegalArgumentException * @throws SQLException @@ -410,6 +411,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @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)} * @throws IdentifierException * @throws IllegalArgumentException * @throws SQLException @@ -785,7 +787,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * Delete a specific DOI in the registration agency records via the DOI Connector * @param context - DSpace context * @param identifier - String containing identifier to delete - * @throws IdentifierException + * @throws DOIIdentifierException */ public void deleteOnline(Context context, String identifier) throws DOIIdentifierException { String doi = doiService.formatIdentifier(identifier); @@ -826,7 +828,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * {@link org.dspace.identifier.service.DOIService#formatIdentifier(String)}. * @return Null if the DOI couldn't be found or the associated DSpaceObject. * @throws SQLException if database error - * @throws IdentifierException If {@code identifier} is null or an empty string. + * @throws DOIIdentifierException If {@code identifier} is null or an empty string. * @throws IllegalArgumentException If the identifier couldn't be recognized as DOI. */ public DSpaceObject getObjectByDOI(Context context, String identifier) @@ -876,10 +878,10 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } /** - * Load a DOI from the database or creates it if it does not exist. This - * method can be used to ensure that a DOI exists in the database and to - * load the appropriate TableRow. As protected method we don't check if the - * DOI is in a decent format, use DOI.formatIdentifier(String) if necessary. + * Load a DOI from the database or creates it if it does not exist. + * This method can be used to ensure that a DOI exists in the database and + * to load the appropriate TableRow. As protected method we don't check if + * the DOI is in a decent format, use DOI.formatIdentifier(String) if necessary. * * @param context The relevant DSpace Context. * @param dso The DSpaceObject the DOI should be loaded or created for. @@ -889,6 +891,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @throws SQLException In case of an error using the database. * @throws DOIIdentifierException If {@code doi} is not part of our prefix or * DOI is registered for another object already. + * @throws IdentifierNotApplicableException passed through. */ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier) throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { @@ -896,11 +899,13 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { } /** - * Load DOI from database, or create one if it doesn't yet exist - * We need to distinguish several cases. LoadOrCreate can be called with a specifid identifier to load or create. - * It can also be used to create a new unspecified identifier. In the latter case doiIdentifier is set null. - * If doiIdentifier is set, we know which doi we should try to load or create, but even in sucha situation - * we might be able to find it in the database or might have to create it. + * Load DOI from database, or create one if it doesn't yet exist. + * We need to distinguish several cases.LoadOrCreate can be called with a + * specified identifier to load or create. It can also be used to create a + * new unspecified identifier. In the latter case doiIdentifier is set null. + * If doiIdentifier is set, we know which doi we should try to load or + * create, but even in such a situation we might be able to find it in the + * database or might have to create it. * * @param context - DSpace context * @param dso - DSpaceObject to identify @@ -909,6 +914,7 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { * @return * @throws SQLException * @throws DOIIdentifierException + * @throws org.dspace.identifier.IdentifierNotApplicableException passed through. */ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, boolean skipFilter) throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { @@ -929,11 +935,11 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { && doi.getResourceTypeId() != dso.getType()) { // doi was assigned to another resource type. Don't // reactivate it - throw new DOIIdentifierException("Cannot reassing " - + "previously deleted DOI " + doiIdentifier - + " as the resource types of the object it was " - + "previously assigned to and the object it " - + "shall be assigned to now divert (was: " + throw new DOIIdentifierException("Cannot reassign" + + " previously deleted DOI " + doiIdentifier + + " as the resource types of the object it was" + + " previously assigned to and the object it" + + " shall be assigned to now differ (was: " + Constants.typeText[doi.getResourceTypeId()] + ", trying to assign to " + Constants.typeText[dso.getType()] + ").", diff --git a/dspace-api/src/main/java/org/dspace/license/LicenseCleanup.java b/dspace-api/src/main/java/org/dspace/license/LicenseCleanup.java index 55eeb8d314..0eac224bcd 100644 --- a/dspace-api/src/main/java/org/dspace/license/LicenseCleanup.java +++ b/dspace-api/src/main/java/org/dspace/license/LicenseCleanup.java @@ -15,6 +15,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.Iterator; import java.util.List; @@ -38,7 +39,8 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Context; /** - * Cleanup class for CC Licenses, corrects XML formating errors by replacing the license_rdf bitstream. + * Cleanup class for CC Licenses, corrects XML formatting errors by replacing + * the license_rdf bitstream. * * @author mdiggory */ @@ -130,7 +132,7 @@ public class LicenseCleanup { AuthorizeException, IOException { List bundles = itemService.getBundles(item, "CC-LICENSE"); - if (bundles == null || bundles.size() == 0) { + if (bundles == null || bundles.isEmpty()) { return; } @@ -138,7 +140,7 @@ public class LicenseCleanup { Bitstream bitstream = bundleService.getBitstreamByName(bundle, "license_rdf"); - String license_rdf = new String(copy(context, bitstream)); + String license_rdf = new String(copy(context, bitstream), StandardCharsets.UTF_8); /* quickly fix xml by ripping out offensive parts */ license_rdf = license_rdf.replaceFirst(" + + + + + DEBUG + + + INFO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From edd310f536f676e9e239e6eac19285f19bcbb34f Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 10 Sep 2021 12:51:24 -0400 Subject: [PATCH 0238/1254] Address Error Prone complaints about tests. (#3061) --- .../java/org/dspace/app/bulkedit/MetadataImportIT.java | 4 +++- .../test/java/org/dspace/app/sherpa/MockSHERPAService.java | 7 ++++--- .../test/java/org/dspace/authority/orcid/MockOrcid.java | 3 +++ .../org/dspace/content/MetadataFieldPerformanceTest.java | 4 ++-- .../src/test/java/org/dspace/discovery/DiscoveryIT.java | 2 +- .../impl/MockPubmedImportMetadataSourceServiceImpl.java | 4 +++- .../org/dspace/util/DSpaceConfigurationInitializer.java | 4 ---- .../test/java/org/dspace/util/DSpaceKernelInitializer.java | 2 +- 8 files changed, 17 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java index e073004f7d..280f4f4ff6 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportIT.java @@ -187,7 +187,7 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { item.getID().toString() + "," + collection.getHandle() + "," + item.getName() + ","}; performImportScript(csv); item = findItemByName("title"); - assertEquals(itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY).size(), 0); + assertEquals(0, itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY).size()); } private Item findItemByName(String name) throws SQLException { @@ -203,6 +203,8 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase { /** * Import mocked CSVs to test item creation behavior, deleting temporary file afterward. + * @param csv content for test file. + * @throws java.lang.Exception passed through. */ public void performImportScript(String[] csv) throws Exception { File csvFile = File.createTempFile("dspace-test-import", "csv"); diff --git a/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java b/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java index d7c4877fa5..b218ba82fe 100644 --- a/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java +++ b/dspace-api/src/test/java/org/dspace/app/sherpa/MockSHERPAService.java @@ -17,8 +17,9 @@ import org.dspace.app.sherpa.v2.SHERPAResponse; /** * Mock implementation for SHERPA API service (used by SHERPA submit service to check - * journal policies) - * This class will return mock SHERPA responses so they can be parsed and turned into external data objects downstream + * journal policies). + * This class will return mock SHERPA responses so they can be parsed and turned + * into external data objects downstream. * * @author Kim Shepherd */ @@ -110,7 +111,7 @@ public class MockSHERPAService extends SHERPAService { try { // Prepare the URI - this will not be used but should be evaluated // in case a syntax exception is thrown - URI uri = prepareQuery(value, endpoint, apiKey); + URI unuseduri = prepareQuery(value, endpoint, apiKey); // Get mock JSON - in this case, a known good result for PLOS content = getClass().getResourceAsStream("plos.json"); diff --git a/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java b/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java index 2f551315e1..562aa86a58 100644 --- a/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java +++ b/dspace-api/src/test/java/org/dspace/authority/orcid/MockOrcid.java @@ -31,18 +31,21 @@ public class MockOrcid extends Orcidv3SolrAuthorityImpl { OrcidRestConnector orcidRestConnector = Mockito.mock(OrcidRestConnector.class); when(orcidRestConnector.get(ArgumentMatchers.startsWith("search?"), ArgumentMatchers.any())) .thenAnswer(new Answer() { + @Override public InputStream answer(InvocationOnMock invocation) { return this.getClass().getResourceAsStream("orcid-search-noresults.xml"); } }); when(orcidRestConnector.get(ArgumentMatchers.startsWith("search?q=Bollini"), ArgumentMatchers.any())) .thenAnswer(new Answer() { + @Override public InputStream answer(InvocationOnMock invocation) { return this.getClass().getResourceAsStream("orcid-search.xml"); } }); when(orcidRestConnector.get(ArgumentMatchers.endsWith("/person"), ArgumentMatchers.any())) .thenAnswer(new Answer() { + @Override public InputStream answer(InvocationOnMock invocation) { return this.getClass().getResourceAsStream("orcid-person-record.xml"); } diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataFieldPerformanceTest.java b/dspace-api/src/test/java/org/dspace/content/MetadataFieldPerformanceTest.java index d9b216f638..3ba7f0556b 100644 --- a/dspace-api/src/test/java/org/dspace/content/MetadataFieldPerformanceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/MetadataFieldPerformanceTest.java @@ -50,7 +50,7 @@ public class MetadataFieldPerformanceTest extends AbstractUnitTest { Assert.assertTrue("Duration (" + duration + ") should be smaller than " + maxDuration + " for " + amount + " tests." + " Max of " + maxDurationPerCall + " ms per operation exceeded: " + - (((double) (duration)) / amount) + " ms.", duration < maxDuration); + (((double) duration) / amount) + " ms.", duration < maxDuration); } @Test @@ -81,7 +81,7 @@ public class MetadataFieldPerformanceTest extends AbstractUnitTest { Assert.assertTrue("Duration (" + duration + ") should be smaller than " + maxDuration + " for " + amount + " tests." + " Max of " + maxDurationPerCall + " ms per operation exceeded: " + - (((double) (duration)) / amount) + " ms.", duration < maxDuration); + (((double) duration) / amount) + " ms.", duration < maxDuration); context.turnOffAuthorisationSystem(); // Delete community & collection created in init() diff --git a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java index 9518f59071..9504d01393 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java +++ b/dspace-api/src/test/java/org/dspace/discovery/DiscoveryIT.java @@ -665,7 +665,7 @@ public class DiscoveryIT extends AbstractIntegrationTestWithDatabase { throws SQLException, AuthorizeException, IOException, WorkflowException, SearchServiceException { context.turnOffAuthorisationSystem(); workspaceItem = context.reloadEntity(workspaceItem); - XmlWorkflowItem workflowItem = workflowService.startWithoutNotify(context, workspaceItem); + XmlWorkflowItem unusedWorkflowItem = workflowService.startWithoutNotify(context, workspaceItem); context.commit(); indexer.commit(); context.restoreAuthSystemState(); diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/MockPubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/MockPubmedImportMetadataSourceServiceImpl.java index a143ed7d34..1a88c1e55b 100644 --- a/dspace-api/src/test/java/org/dspace/external/provider/impl/MockPubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/MockPubmedImportMetadataSourceServiceImpl.java @@ -14,6 +14,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; @@ -56,6 +57,7 @@ public class MockPubmedImportMetadataSourceServiceImpl extends PubmedImportMetad }); when(pubmedWebTarget.request(ArgumentMatchers.any(MediaType.class))) .thenAnswer(new Answer() { + @Override public Invocation.Builder answer(InvocationOnMock invocation) throws Throwable { Invocation.Builder builder = Mockito.mock(Invocation.Builder.class); when(builder.get()).thenAnswer(new Answer() { @@ -67,7 +69,7 @@ public class MockPubmedImportMetadataSourceServiceImpl extends PubmedImportMetad public String answer(InvocationOnMock invocation) throws Throwable { String resourceName = "pubmed-" + valueCapture.getValue() + ".xml"; InputStream resource = getClass().getResourceAsStream(resourceName); - try (Reader reader = new InputStreamReader(resource)) { + try (Reader reader = new InputStreamReader(resource, StandardCharsets.UTF_8)) { return FileCopyUtils.copyToString(reader); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java index 70d8f12990..e2e0355f12 100644 --- a/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java +++ b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java @@ -9,8 +9,6 @@ package org.dspace.util; import org.apache.commons.configuration2.Configuration; import org.apache.commons.configuration2.spring.ConfigurationPropertySource; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.context.ApplicationContextInitializer; @@ -32,8 +30,6 @@ import org.springframework.context.ConfigurableApplicationContext; public class DSpaceConfigurationInitializer implements ApplicationContextInitializer { - private static final Logger log = LogManager.getLogger(); - @Override public void initialize(final ConfigurableApplicationContext applicationContext) { // Load DSpace Configuration service (requires kernel already initialized) diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java index 93fd308185..a6f381bafb 100644 --- a/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java +++ b/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java @@ -115,7 +115,7 @@ public class DSpaceKernelInitializer /** * Utility class that will destroy the DSpace Kernel on Spring shutdown. */ - private class DSpaceKernelDestroyer + private static class DSpaceKernelDestroyer implements ApplicationListener { private DSpaceKernel kernel; From bb020e623a770c3c812ed198a28287f825f5cce5 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 11 Sep 2021 12:51:06 +0200 Subject: [PATCH 0239/1254] added other tests --- .../rest/RelationshipRestRepositoryIT.java | 151 +++++++++++++++++- 1 file changed, 149 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index 0cb731930a..78a6a629d0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -31,6 +31,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -2940,7 +2941,7 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest .withLeftPlace(1) .build(); - context.restoreAuthSystemState(); + context.restoreAuthSystemState(); // by left relation getClient().perform(get("/api/core/relationships/search/byItemsAndType") @@ -2992,7 +2993,7 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest .withLeftPlace(1) .build(); - context.restoreAuthSystemState(); + context.restoreAuthSystemState(); // missing relationshipLabel getClient().perform(get("/api/core/relationships/search/byItemsAndType") @@ -3029,4 +3030,150 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest .andExpect(status().isBadRequest()); } + @Test + public void findByItemsAndTypeUnprocessableEntityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndTypeName(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, author3, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(1) + .build(); + RelationshipBuilder.createRelationshipBuilder(context, publication1, author1, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(1) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", isAuthorOfPublicationRelationshipType.getID().toString()) + .param("relationshipLabel", "wrongLabel") + .param("focusItem", orgUnit1.getID().toString()) + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void findByItemsAndTypeEmptyResponceTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndTypeName(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, author3, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(1) + .build(); + RelationshipBuilder.createRelationshipBuilder(context, publication1, author1, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(1) + .build(); + + context.restoreAuthSystemState(); + + Integer typeId = Integer.MAX_VALUE; + + // with typeId that does not exist + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", typeId.toString()) + .param("relationshipLabel", "isAuthorOfPublication") + .param("focusItem", publication1.getID().toString()) + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships").doesNotExist()); + + // with focus item that does not exist + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", isAuthorOfPublicationRelationshipType.getID().toString()) + .param("relationshipLabel", "isAuthorOfPublication") + .param("focusItem", UUID.randomUUID().toString()) + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships").doesNotExist()); + } + + @Test + public void findByItemsAndTypePaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndTypeName(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + Relationship relationship1 = RelationshipBuilder.createRelationshipBuilder(context, publication1, author3, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(2) + .build(); + Relationship relationship2 = RelationshipBuilder.createRelationshipBuilder(context, publication1, author1, + isAuthorOfPublicationRelationshipType) + .withLeftPlace(2) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", isAuthorOfPublicationRelationshipType.getID().toString()) + .param("relationshipLabel", "isAuthorOfPublication") + .param("focusItem", publication1.getID().toString()) + .param("size", "1") + .param("sort", "id, DESC") + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships", contains( + RelationshipMatcher.matchRelationshipValues(relationship2) + ))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", isAuthorOfPublicationRelationshipType.getID().toString()) + .param("relationshipLabel", "isAuthorOfPublication") + .param("focusItem", publication1.getID().toString()) + .param("page", "1") + .param("size", "1") + .param("sort", "id, DESC") + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships", contains( + RelationshipMatcher.matchRelationshipValues(relationship1) + ))) + .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient().perform(get("/api/core/relationships/search/byItemsAndType") + .param("typeId", isAuthorOfPublicationRelationshipType.getID().toString()) + .param("relationshipLabel", "isAuthorOfPublication") + .param("focusItem", publication1.getID().toString()) + .param("page", "5") + .param("relatedItem", author1.getID().toString(), + author2.getID().toString(), + author3.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships").doesNotExist()) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.number", is(5))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + } + } From 25377a639b5047f89facec85b4f520024bb4f86d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 11 Sep 2021 13:32:02 +0200 Subject: [PATCH 0240/1254] fix pagination --- .../content/RelationshipServiceImpl.java | 12 +++++++-- .../dspace/content/dao/RelationshipDAO.java | 26 +++++++++++++++++-- .../content/dao/impl/RelationshipDAOImpl.java | 21 +++++++++++++-- .../content/service/RelationshipService.java | 26 +++++++++++++++++-- .../org/dspace/core/AbstractHibernateDAO.java | 16 ++++++++++++ .../RelationshipRestRepository.java | 9 +++++-- 6 files changed, 100 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index c7971716a4..a0878f49c2 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -711,8 +711,16 @@ public class RelationshipServiceImpl implements RelationshipService { @Override public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, - RelationshipType relationshipType, List items, boolean isLeft) throws SQLException { + RelationshipType relationshipType, List items, boolean isLeft, + int offset, int limit) throws SQLException { return relationshipDAO - .findByItemAndRelationshipTypeAndList(context, focusUUID, relationshipType, items, isLeft); + .findByItemAndRelationshipTypeAndList(context, focusUUID, relationshipType, items, isLeft, offset,limit); + } + + @Override + public int countByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, RelationshipType relationshipType, + List items, boolean isLeft) throws SQLException { + return relationshipDAO + .countByItemAndRelationshipTypeAndList(context, focusUUID, relationshipType, items, isLeft); } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java index 0e877d9913..57b950a36b 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java @@ -218,6 +218,28 @@ public interface RelationshipDAO extends GenericDAO { throws SQLException; /** + * This method is used to retrieve relationships that match focusItem + * on the one hand and matches list of related items elsewhere. + * + * @param context DSpace context object + * @param focusUUID UUID of Item that will match left side if the param isLeft is true otherwise right side + * @param relationshipType Relationship type to filter by + * @param items List of UUID that will use to filter other side respect the focusUUID + * @param isLeft Indicating whether the counted Relationships should have + * the given Item on the left side or not + * @param limit paging limit + * @param offset paging offset + * @return + * @throws SQLException If database error + */ + List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, + RelationshipType relationshipType, List items, boolean isLeft, + int offset, int limit) throws SQLException; + + /** + * Count total number of relationships that match focusItem + * on the one hand and matches list of related items elsewhere. + * * @param context DSpace context object * @param focusUUID UUID of Item that will match left side if the param isLeft is true otherwise right side * @param relationshipType Relationship type to filter by @@ -227,6 +249,6 @@ public interface RelationshipDAO extends GenericDAO { * @return * @throws SQLException If database error */ - List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, - RelationshipType relationshipType, List items, boolean isLeft) throws SQLException; + int countByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, RelationshipType relationshipType, + List items, boolean isLeft) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index e9bffc0d13..318ee63701 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -268,7 +268,8 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl @Override public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, - RelationshipType relationshipType, List items, boolean isLeft) throws SQLException { + RelationshipType relationshipType, List items, boolean isLeft, + int offset, int limit) throws SQLException { String side = isLeft ? "left_id" : "right_id"; String otherSide = !isLeft ? "left_id" : "right_id"; Query query = createQuery(context, "FROM " + Relationship.class.getSimpleName() + @@ -278,7 +279,23 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl query.setParameter("typeId", relationshipType.getID()); query.setParameter("focusUUID", focusUUID); query.setParameter("list", items); - return list(query); + return list(query, limit, offset); + } + + @Override + public int countByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, RelationshipType relationshipType, + List items, boolean isLeft) throws SQLException { + String side = isLeft ? "left_id" : "right_id"; + String otherSide = !isLeft ? "left_id" : "right_id"; + Query query = createQuery(context, "SELECT count(*) " + + "FROM " + Relationship.class.getSimpleName() + + " WHERE type_id = (:typeId) " + + "AND " + side + " = (:focusUUID) " + + "AND " + otherSide + " in (:list)"); + query.setParameter("typeId", relationshipType.getID()); + query.setParameter("focusUUID", focusUUID); + query.setParameter("list", items); + return count(query); } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index 9223fe17fc..6a100e2a3d 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -338,6 +338,28 @@ public interface RelationshipService extends DSpaceCRUDService { throws SQLException, AuthorizeException; /** + * This method is used to retrieve relationships that match focusItem + * on the one hand and matches list of related items elsewhere. + * + * @param context DSpace context object + * @param focusUUID UUID of Item that will match left side if the param isLeft is true otherwise right side + * @param relationshipType Relationship type to filter by + * @param items List of UUID that will use to filter other side respect the focusUUID + * @param isLeft Indicating whether the counted Relationships should have + * the given Item on the left side or not + * @param limit paging limit + * @param offset paging offset + * @return + * @throws SQLException If database error + */ + public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, + RelationshipType relationshipType, List items, boolean isLeft, + int offset, int limit) throws SQLException; + + /** + * Count total number of relationships that match focusItem + * on the one hand and matches list of related items elsewhere. + * * @param context DSpace context object * @param focusUUID UUID of Item that will match left side if the param isLeft is true otherwise right side * @param relationshipType Relationship type to filter by @@ -347,7 +369,7 @@ public interface RelationshipService extends DSpaceCRUDService { * @return * @throws SQLException If database error */ - public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, - RelationshipType relationshipType, List items, boolean isLeft) throws SQLException; + public int countByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, RelationshipType relationshipType, + List items, boolean isLeft) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java index 9f97dce1ce..adeb4d09d8 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java @@ -204,6 +204,22 @@ public abstract class AbstractHibernateDAO implements GenericDAO { return result; } + /** + * This method will return a list of results for the given Query and parameters + * + * @param query The query for which the resulting list will be returned + * @param limit The maximum amount of results to be returned + * @param offset The offset to be used for the Query + * @return A list of results determined by the Query and parameters + */ + public List list(Query query, int limit, int offset) { + query.setFirstResult(offset); + query.setMaxResults(limit); + @SuppressWarnings("unchecked") + List result = (List) query.getResultList(); + return result; + } + /** * Retrieve a unique result from the query. If multiple results CAN be * retrieved an exception will be thrown, so only use when the criteria diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java index 3bcb57bd74..0fdf8eb7e2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -379,6 +379,7 @@ public class RelationshipRestRepository extends DSpaceRestRepository items, Pageable pageable) throws SQLException { Context context = obtainContext(); + int total = 0; List relationships = new LinkedList<>(); RelationshipType relationshipType = relationshipTypeService.find(context, typeId); if (Objects.nonNull(relationshipType)) { @@ -388,10 +389,14 @@ public class RelationshipRestRepository extends DSpaceRestRepository(items), relationshipType.getLeftwardType().equals(label)); + relationshipType, new ArrayList(items), relationshipType.getLeftwardType().equals(label), + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getPageSize())); + total = relationshipService.countByItemAndRelationshipTypeAndList(context, focusUUID, + relationshipType, new ArrayList(items), relationshipType.getLeftwardType().equals(label)); } - return converter.toRestPage(relationships, pageable, utils.obtainProjection()); + return converter.toRestPage(relationships, pageable, total, utils.obtainProjection()); } } From 7b3db9da4fd1481c1c23092be53ce32f34412256 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 11 Sep 2021 15:15:00 +0200 Subject: [PATCH 0241/1254] added ORDER BY id to sql query --- .../org/dspace/content/dao/impl/RelationshipDAOImpl.java | 3 ++- .../org/dspace/app/rest/RelationshipRestRepositoryIT.java | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index 318ee63701..48baf45f23 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -275,7 +275,8 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl Query query = createQuery(context, "FROM " + Relationship.class.getSimpleName() + " WHERE type_id = (:typeId) " + "AND " + side + " = (:focusUUID) " + - "AND " + otherSide + " in (:list)"); + "AND " + otherSide + " in (:list) " + + "ORDER BY id"); query.setParameter("typeId", relationshipType.getID()); query.setParameter("focusUUID", focusUUID); query.setParameter("list", items); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index 78a6a629d0..61d57d34c9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -3130,13 +3130,12 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest .param("relationshipLabel", "isAuthorOfPublication") .param("focusItem", publication1.getID().toString()) .param("size", "1") - .param("sort", "id, DESC") .param("relatedItem", author1.getID().toString(), author2.getID().toString(), author3.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.relationships", contains( - RelationshipMatcher.matchRelationshipValues(relationship2) + RelationshipMatcher.matchRelationshipValues(relationship1) ))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -3148,13 +3147,12 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest .param("focusItem", publication1.getID().toString()) .param("page", "1") .param("size", "1") - .param("sort", "id, DESC") .param("relatedItem", author1.getID().toString(), author2.getID().toString(), author3.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.relationships", contains( - RelationshipMatcher.matchRelationshipValues(relationship1) + RelationshipMatcher.matchRelationshipValues(relationship2) ))) .andExpect(jsonPath("$.page.number", is(1))) .andExpect(jsonPath("$.page.totalPages", is(2))) From c3274d1704cd956603fc2d92ba62450a40b7dcc2 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sun, 12 Sep 2021 14:22:23 +0200 Subject: [PATCH 0242/1254] renamed service methods --- .../java/org/dspace/content/RelationshipServiceImpl.java | 6 +++--- .../org/dspace/content/service/RelationshipService.java | 6 +++--- .../app/rest/repository/RelationshipRestRepository.java | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index a0878f49c2..8c592eefe7 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -710,7 +710,7 @@ public class RelationshipServiceImpl implements RelationshipService { } @Override - public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, + public List findByItemRelationshipTypeAndRelatedList(Context context, UUID focusUUID, RelationshipType relationshipType, List items, boolean isLeft, int offset, int limit) throws SQLException { return relationshipDAO @@ -718,8 +718,8 @@ public class RelationshipServiceImpl implements RelationshipService { } @Override - public int countByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, RelationshipType relationshipType, - List items, boolean isLeft) throws SQLException { + public int countByItemRelationshipTypeAndRelatedList(Context context, UUID focusUUID, + RelationshipType relationshipType, List items, boolean isLeft) throws SQLException { return relationshipDAO .countByItemAndRelationshipTypeAndList(context, focusUUID, relationshipType, items, isLeft); } diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index 6a100e2a3d..2e0bb6f2be 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -352,7 +352,7 @@ public interface RelationshipService extends DSpaceCRUDService { * @return * @throws SQLException If database error */ - public List findByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, + public List findByItemRelationshipTypeAndRelatedList(Context context, UUID focusUUID, RelationshipType relationshipType, List items, boolean isLeft, int offset, int limit) throws SQLException; @@ -369,7 +369,7 @@ public interface RelationshipService extends DSpaceCRUDService { * @return * @throws SQLException If database error */ - public int countByItemAndRelationshipTypeAndList(Context context, UUID focusUUID, RelationshipType relationshipType, - List items, boolean isLeft) throws SQLException; + public int countByItemRelationshipTypeAndRelatedList(Context context, UUID focusUUID, + RelationshipType relationshipType, List items, boolean isLeft) throws SQLException; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java index 0fdf8eb7e2..00dd0c8893 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -388,12 +388,12 @@ public class RelationshipRestRepository extends DSpaceRestRepository(items), relationshipType.getLeftwardType().equals(label), Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - total = relationshipService.countByItemAndRelationshipTypeAndList(context, focusUUID, + total = relationshipService.countByItemRelationshipTypeAndRelatedList(context, focusUUID, relationshipType, new ArrayList(items), relationshipType.getLeftwardType().equals(label)); } return converter.toRestPage(relationships, pageable, total, utils.obtainProjection()); From 4cc369386726c3d5346e426737000898b928c210 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Sep 2021 14:39:35 +0200 Subject: [PATCH 0243/1254] added security on item version link end point --- .../repository/ItemVersionLinkRepository.java | 2 +- ...orOfAInprogressSubmissionInformations.java | 35 ++++++++++++++- .../VersionRestPermissionEvaluatorPlugin.java | 3 +- .../dspace/app/rest/ItemRestRepositoryIT.java | 44 +++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java index 2f72f00fe8..7289745b1c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java @@ -49,7 +49,7 @@ public class ItemVersionLinkRepository extends AbstractDSpaceRestRepository * itemUuid param as UUID * @throws SQLException If something goes wrong */ - @PreAuthorize("hasPermission(#itemUuid, 'ITEM', 'READ')") + @PreAuthorize("hasPermission(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid), 'VERSION', 'READ')") public VersionRest getItemVersion(@Nullable HttpServletRequest request, UUID itemUuid, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java index a01ce88d80..003370b05b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java @@ -8,19 +8,25 @@ package org.dspace.app.rest.security; import java.sql.SQLException; import java.util.Objects; +import java.util.UUID; import javax.annotation.Nullable; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.model.WorkflowItemRest; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; +import org.dspace.services.RequestService; import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; +import org.dspace.versioning.service.VersioningService; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; @@ -42,6 +48,15 @@ public class ExtractorOfAInprogressSubmissionInformations { @Autowired private VersionHistoryService versionHistoryService; + @Autowired + private VersioningService versionService; + + @Autowired + private ItemService itemService; + + @Autowired + private RequestService requestService; + public Integer getAInprogressSubmissionID(@Nullable HttpServletRequest request, Integer versionHistoryId) { Context context = getContext(request); if (Objects.nonNull(versionHistoryId)) { @@ -88,8 +103,26 @@ public class ExtractorOfAInprogressSubmissionInformations { return StringUtils.EMPTY; } + public Integer getVersionIdByItemUUID(@Nullable HttpServletRequest request, UUID uuid) { + Context context = getContext(request); + if (Objects.nonNull(uuid)) { + try { + Item item = itemService.find(context, uuid); + if (Objects.nonNull(item)) { + Version version = versionService.getVersion(context, item); + return Objects.nonNull(version) ? version.getID() : null; + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + return null; + } + private Context getContext(HttpServletRequest request) { - return Objects.nonNull(request) ? ContextUtil.obtainContext(request) : null; + ServletRequest currentRequest = requestService.getCurrentRequest().getServletRequest(); + return Objects.nonNull(request) ? ContextUtil.obtainContext(request) + : ContextUtil.obtainContext(currentRequest); } } \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java index 9b98718120..687de8c05e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.security; import java.io.Serializable; import java.sql.SQLException; +import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.VersionRest; @@ -53,7 +54,7 @@ public class VersionRestPermissionEvaluatorPlugin extends RestObjectPermissionEv DSpaceRestPermission restPermission) { - if (!StringUtils.equalsIgnoreCase(targetType, VersionRest.NAME)) { + if (!StringUtils.equalsIgnoreCase(targetType, VersionRest.NAME) || Objects.isNull(targetId)) { return false; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index f6549dfa3c..e37aca0877 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -78,6 +78,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.workflow.WorkflowItem; import org.hamcrest.Matcher; @@ -91,6 +92,9 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private CollectionService collectionService; + @Autowired + private ConfigurationService configurationService; + private Item publication1; private Item author1; private Item author2; @@ -4147,6 +4151,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void findVersionItemUnauthorizedTest() throws Exception { + configurationService.setProperty("versioning.item.history.view.admin", true); + context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -4169,10 +4175,14 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/core/items/" + item.getID() + "/version")) .andExpect(status().isUnauthorized()); + + configurationService.setProperty("versioning.item.history.view.admin", true); } @Test public void findVersionForItemForbiddenTest() throws Exception { + configurationService.setProperty("versioning.item.history.view.admin", true); + context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -4197,5 +4207,39 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { String epersonToken = getAuthToken(eperson.getEmail(), password); getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/version")) .andExpect(status().isForbidden()); + + configurationService.setProperty("versioning.item.history.view.admin", true); } + + @Test + public void findVersionForItemBadRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionBuilder.createVersion(context, item, "test").build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/items/wrongID/version")) + .andExpect(status().isBadRequest()); + + getClient(epersonToken).perform(get("/api/core/items/1/version")) + .andExpect(status().isBadRequest()); + } + } From 7cc4b1db77079701bea5677ecce3d53a7442a1e8 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Sep 2021 17:44:02 +0200 Subject: [PATCH 0244/1254] fix javaDoc --- .../org/dspace/versioning/service/VersioningService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java b/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java index 8a85f960bd..32f673abf8 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java +++ b/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java @@ -46,10 +46,10 @@ public interface VersioningService { * Delete a Version * * @param context context - * @param Version version + * @param version version * @throws SQLException if database error */ - public void delete(Context c, Version version) throws SQLException; + public void delete(Context context, Version version) throws SQLException; void removeVersion(Context c, Item item) throws SQLException; @@ -70,7 +70,7 @@ public interface VersioningService { * Update the Version * * @param context context - * @param Version version + * @param version version * @throws SQLException if database error */ public void update(Context context, Version version) throws SQLException; From cd053ab9ed019677c66208dec472be6d0c67f253 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Sep 2021 17:47:38 +0200 Subject: [PATCH 0245/1254] added test to verify proper functioning of canCreateVersion feature --- .../CanCreateVersionFeatureIT.java | 95 ++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java index 9ead8f8706..1f6c84c4c7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java @@ -144,13 +144,14 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) .andExpect(jsonPath("$._embedded").exists()); + + configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); } @Test public void submitterItemWithPropertySubmitterCanCreateNewVersionIsFalseTest() throws Exception { context.turnOffAuthorisationSystem(); - configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); itemA.setSubmitter(user); context.restoreAuthSystemState(); @@ -166,7 +167,6 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest } @Test - @SuppressWarnings("unchecked") public void checkCanCreateVersionsFeatureTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -215,5 +215,96 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest getClient(tokenUser).perform(get("/api/authz/authorizations/" + user2ItemB.getID())) .andExpect(status().isNotFound()); + configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); } + + @Test + public void checkCanCreateVersionsFeature999Test() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson adminComA = EPersonBuilder.createEPerson(context) + .withEmail("testComAdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminComB = EPersonBuilder.createEPerson(context) + .withEmail("testComBdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withEmail("testCol1Admin@test.com") + .withPassword(password) + .build(); + + EPerson adminCol2 = EPersonBuilder.createEPerson(context) + .withEmail("testCol2Admin@test.com") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community A") + .withAdminGroup(adminComA) + .build(); + + CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community B") + .withAdminGroup(adminComB) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withAdminGroup(adminCol1) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 2") + .withAdminGroup(adminCol2) + .build(); + + Item itemA = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + ItemRest itemRestA = itemConverter.convert(itemA, DefaultProjection.DEFAULT); + + String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); + String tokenAdminComB = getAuthToken(adminComB.getEmail(), password); + String tokenAdminCol1 = getAuthToken(adminCol1.getEmail(), password); + String tokenAdminCol2 = getAuthToken(adminCol2.getEmail(), password); + + // define authorizations that we know must exists + Authorization adminOfComAToItemA = new Authorization(adminComA, canCreateVersionFeature, itemRestA); + Authorization adminOfCol1ToItemA = new Authorization(adminCol1, canCreateVersionFeature, itemRestA); + + // define authorization that we know not exists + Authorization adminOfComBToItemA = new Authorization(adminComB, canCreateVersionFeature, itemRestA); + Authorization adminOfCol2ToItemA = new Authorization(adminCol2, canCreateVersionFeature, itemRestA); + + getClient(tokenAdminComA).perform(get("/api/authz/authorizations/" + adminOfComAToItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfComAToItemA)))); + + getClient(tokenAdminCol1).perform(get("/api/authz/authorizations/" + adminOfCol1ToItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfCol1ToItemA)))); + + getClient(tokenAdminComB).perform(get("/api/authz/authorizations/" + adminOfComBToItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminCol2).perform(get("/api/authz/authorizations/" + adminOfCol2ToItemA.getID())) + .andExpect(status().isNotFound()); + } + } \ No newline at end of file From 0b91b40f546fc391d29477f4301714d0fb64a7e8 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Sep 2021 17:48:15 +0200 Subject: [PATCH 0246/1254] refactored canCreateVersion feature --- .../impl/CanCreateVersionFeature.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java index 7c6763244b..73436104e5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java @@ -45,6 +45,7 @@ public class CanCreateVersionFeature implements AuthorizationFeature { private ItemService itemService; @Override + @SuppressWarnings("rawtypes") public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof ItemRest) { EPerson currentUser = context.getCurrentUser(); @@ -54,12 +55,16 @@ public class CanCreateVersionFeature implements AuthorizationFeature { if (authorizeService.isAdmin(context)) { return true; } - if (configurationService.getBooleanProperty("versioning.submitterCanCreateNewVersion")) { - Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); - EPerson submitter = item.getSubmitter(); - return Objects.nonNull(submitter) && currentUser.getID().equals(submitter.getID()); + Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); + if (Objects.nonNull(item)) { + if (authorizeService.isAdmin(context, item)) { + return true; + } + if (configurationService.getBooleanProperty("versioning.submitterCanCreateNewVersion")) { + EPerson submitter = item.getSubmitter(); + return Objects.nonNull(submitter) && currentUser.getID().equals(submitter.getID()); + } } - } return false; } From cc6255248171e81a6a352d2b924795d66d746a1a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Sep 2021 19:11:03 +0200 Subject: [PATCH 0247/1254] minor fix --- .../authorization/impl/CanCreateVersionFeature.java | 3 --- .../app/rest/repository/VersionRestRepository.java | 2 +- .../org/dspace/app/rest/VersionRestRepositoryIT.java | 10 ++++------ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java index 73436104e5..a8ca12f170 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java @@ -52,9 +52,6 @@ public class CanCreateVersionFeature implements AuthorizationFeature { if (Objects.isNull(currentUser)) { return false; } - if (authorizeService.isAdmin(context)) { - return true; - } Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); if (Objects.nonNull(item)) { if (authorizeService.isAdmin(context, item)) { 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 254fa170d8..9c086a8750 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 @@ -83,7 +83,7 @@ public class VersionRestRepository extends DSpaceRestRepository Date: Tue, 14 Sep 2021 13:14:30 +0200 Subject: [PATCH 0248/1254] implemented CanDeleteVersion feature and tests to to verify proper functioning --- .../impl/CanDeleteVersionFeature.java | 62 +++++ .../repository/VersionRestRepository.java | 13 +- .../CanDeleteVersionFeatureIT.java | 258 ++++++++++++++++++ 3 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java new file mode 100644 index 0000000000..388a1edf9b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.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.app.rest.authorization.impl; +import java.sql.SQLException; +import java.util.Objects; + +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.VersionRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.core.Context; +import org.dspace.versioning.Version; +import org.dspace.versioning.service.VersioningService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The delete version feature. It can be used to verify + * if the user can delete the version of an Item. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component +@AuthorizationFeatureDocumentation(name = CanDeleteVersionFeature.NAME, + description = "It can be used to verify if the user can delete a version of an Item") +public class CanDeleteVersionFeature extends DeleteFeature { + + @Autowired + private ItemConverter itemConverter; + @Autowired + private VersioningService versioningService; + + public static final String NAME = "canDeleteVersion"; + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + if (object instanceof VersionRest) { + Version version = versioningService.getVersion(context, ((VersionRest)object).getId()); + if (Objects.nonNull(version) && Objects.nonNull(version.getItem())) { + ItemRest itemRest = itemConverter.convert(version.getItem(), DefaultProjection.DEFAULT); + return super.isAuthorized(context, itemRest); + } + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + VersionRest.CATEGORY + "." + VersionRest.NAME + }; + } + +} \ No newline at end of file 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 9c086a8750..4983fbc848 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 @@ -49,7 +49,8 @@ import org.springframework.stereotype.Component; * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ @Component(VersionRest.CATEGORY + "." + VersionRest.NAME) -public class VersionRestRepository extends DSpaceRestRepository { +public class VersionRestRepository extends DSpaceRestRepository + implements ReloadableEntityObjectRepository { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(VersionRestRepository.class); @@ -180,4 +181,14 @@ public class VersionRestRepository extends DSpaceRestRepository getPKClass() { + return Integer.class; + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java new file mode 100644 index 0000000000..c4e887abf2 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java @@ -0,0 +1,258 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.junit.Assert.assertNotNull; +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.authorization.impl.CanDeleteVersionFeature; +import org.dspace.app.rest.converter.VersionConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; +import org.dspace.app.rest.model.VersionRest; +import org.dspace.app.rest.projection.DefaultProjection; +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.builder.VersionBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.EPerson; +import org.dspace.versioning.Version; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test for the canDeleteVersion authorization feature. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class CanDeleteVersionFeatureIT extends AbstractControllerIntegrationTest { + + @Autowired + private VersionConverter versionConverter; + @Autowired + private WorkspaceItemService workspaceItemService; + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + @Autowired + private org.dspace.content.service.InstallItemService installItemService; + + private AuthorizationFeature canDeleteVersionFeature; + + final String feature = "canDeleteVersion"; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + canDeleteVersionFeature = authorizationFeatureService.find(CanDeleteVersionFeature.NAME); + + context.restoreAuthSystemState(); + } + + @Test + public void canDeleteVersionsFeatureTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "My test summary").build(); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, version.getItem()); + installItemService.installItem(context, workspaceItem); + + context.restoreAuthSystemState(); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + // define authorizations that we know must exists + Authorization admin2Version = new Authorization(admin, canDeleteVersionFeature, versionRest); + + // define authorization that we know not exists + Authorization eperson2Version = new Authorization(eperson, canDeleteVersionFeature, versionRest); + Authorization anonymous2Version = new Authorization(null, canDeleteVersionFeature, versionRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + admin2Version.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(AuthorizationMatcher.matchAuthorization(admin2Version)))); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2Version.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymous2Version.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void checkCanDeleteVersionsFeatureByColAndComAdminsTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson adminComA = EPersonBuilder.createEPerson(context) + .withEmail("testComAdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminComB = EPersonBuilder.createEPerson(context) + .withEmail("testComBdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withEmail("testCol1Admin@test.com") + .withPassword(password) + .build(); + + EPerson adminCol2 = EPersonBuilder.createEPerson(context) + .withEmail("testCol2Admin@test.com") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community A") + .withAdminGroup(adminComA) + .build(); + + CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community B") + .withAdminGroup(adminComB) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withAdminGroup(adminCol1) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 2") + .withAdminGroup(adminCol2) + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "My test summary").build(); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, version.getItem()); + installItemService.installItem(context, workspaceItem); + + context.restoreAuthSystemState(); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); + String tokenAdminComB = getAuthToken(adminComB.getEmail(), password); + String tokenAdminCol1 = getAuthToken(adminCol1.getEmail(), password); + String tokenAdminCol2 = getAuthToken(adminCol2.getEmail(), password); + + // define authorizations that we know must exists + Authorization adminOfComAToVersion = new Authorization(adminComA, canDeleteVersionFeature, versionRest); + Authorization adminOfCol1ToVersion = new Authorization(adminCol1, canDeleteVersionFeature, versionRest); + + // define authorization that we know not exists + Authorization adminOfComBToVersion = new Authorization(adminComB, canDeleteVersionFeature, versionRest); + Authorization adminOfCol2ToVersion = new Authorization(adminCol2, canDeleteVersionFeature, versionRest); + + getClient(tokenAdminComA).perform(get("/api/authz/authorizations/" + adminOfComAToVersion.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfComAToVersion)))); + + getClient(tokenAdminCol1).perform(get("/api/authz/authorizations/" + adminOfCol1ToVersion.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfCol1ToVersion)))); + + getClient(tokenAdminComB).perform(get("/api/authz/authorizations/" + adminOfComBToVersion.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminCol2).perform(get("/api/authz/authorizations/" + adminOfCol2ToVersion.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void canDeleteVersionsFeatureWithVesionInSubmissionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "My test summary").build(); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, version.getItem()); + + context.restoreAuthSystemState(); + + assertNotNull(workspaceItem); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + // define authorization that we know not exists + Authorization admin2Version = new Authorization(admin, canDeleteVersionFeature, versionRest); + Authorization eperson2Version = new Authorization(eperson, canDeleteVersionFeature, versionRest); + Authorization anonymous2Version = new Authorization(null, canDeleteVersionFeature, versionRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + admin2Version.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2Version.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymous2Version.getID())) + .andExpect(status().isNotFound()); + } + +} \ No newline at end of file From 8e50f172487629f77b22f011331daebcf605f320 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Sep 2021 13:24:55 +0200 Subject: [PATCH 0249/1254] implemented CanEditVersion feature and tests to to verify proper functioning --- .../impl/CanEditVersionFeature.java | 70 ++++++ .../CanEditVersionFeatureIT.java | 201 ++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java new file mode 100644 index 0000000000..1f45df5338 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.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.app.rest.authorization.impl; +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.VersionRest; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.versioning.Version; +import org.dspace.versioning.service.VersioningService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The create version feature. It can be used to verify + * if the user can create the version of an Item. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component +@AuthorizationFeatureDocumentation(name = CanEditVersionFeature.NAME, + description = "It can be used to verify if the user can edit the summary of a version of an Item") +public class CanEditVersionFeature implements AuthorizationFeature { + + public static final String NAME = "canEditVersion"; + + @Autowired + private ItemService itemService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private VersioningService versioningService; + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + if (object instanceof VersionRest) { + EPerson currentUser = context.getCurrentUser(); + if (Objects.isNull(currentUser)) { + return false; + } + Version version = versioningService.getVersion(context, (((VersionRest) object).getId())); + if (Objects.nonNull(version) && Objects.nonNull(version.getItem())) { + Item item = itemService.find(context, version.getItem().getID()); + return Objects.nonNull(item) ? authorizeService.isAdmin(context, item) : false; + } + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + VersionRest.CATEGORY + "." + VersionRest.NAME + }; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java new file mode 100644 index 0000000000..e9510b9332 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java @@ -0,0 +1,201 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.authorization.impl.CanEditVersionFeature; +import org.dspace.app.rest.converter.VersionConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; +import org.dspace.app.rest.model.VersionRest; +import org.dspace.app.rest.projection.DefaultProjection; +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.builder.VersionBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.eperson.EPerson; +import org.dspace.versioning.Version; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test for the canEditVersion authorization feature. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { + + @Autowired + private VersionConverter versionConverter; + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + + private AuthorizationFeature canEditVersionFeature; + + final String feature = "canEditVersion"; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + + canEditVersionFeature = authorizationFeatureService.find(CanEditVersionFeature.NAME); + + context.restoreAuthSystemState(); + } + + @Test + public void canEditVersionsFeatureTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "My test summary").build(); + + context.restoreAuthSystemState(); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + // define authorizations that we know must exists + Authorization admin2Version = new Authorization(admin, canEditVersionFeature, versionRest); + + // define authorization that we know not exists + Authorization eperson2Version = new Authorization(eperson, canEditVersionFeature, versionRest); + Authorization anonymous2Version = new Authorization(null, canEditVersionFeature, versionRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + admin2Version.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(AuthorizationMatcher.matchAuthorization(admin2Version)))); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2Version.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymous2Version.getID())) + .andExpect(status().isNotFound()); + } + + @Test + public void canEditVersionsFeatureByColAndComAdminsTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson adminComA = EPersonBuilder.createEPerson(context) + .withEmail("testComAdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminComB = EPersonBuilder.createEPerson(context) + .withEmail("testComBdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withEmail("testCol1Admin@test.com") + .withPassword(password) + .build(); + + EPerson adminCol2 = EPersonBuilder.createEPerson(context) + .withEmail("testCol2Admin@test.com") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community A") + .withAdminGroup(adminComA) + .build(); + + CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community B") + .withAdminGroup(adminComB) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withAdminGroup(adminCol1) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 2") + .withAdminGroup(adminCol2) + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "My test summary").build(); + + context.restoreAuthSystemState(); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); + String tokenAdminComB = getAuthToken(adminComB.getEmail(), password); + String tokenAdminCol1 = getAuthToken(adminCol1.getEmail(), password); + String tokenAdminCol2 = getAuthToken(adminCol2.getEmail(), password); + + // define authorizations that we know must exists + Authorization adminOfComAToVersion = new Authorization(adminComA, canEditVersionFeature, versionRest); + Authorization adminOfCol1ToVersion = new Authorization(adminCol1, canEditVersionFeature, versionRest); + + // define authorization that we know not exists + Authorization adminOfComBToVersion = new Authorization(adminComB, canEditVersionFeature, versionRest); + Authorization adminOfCol2ToVersion = new Authorization(adminCol2, canEditVersionFeature, versionRest); + + getClient(tokenAdminComA).perform(get("/api/authz/authorizations/" + adminOfComAToVersion.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfComAToVersion)))); + + getClient(tokenAdminCol1).perform(get("/api/authz/authorizations/" + adminOfCol1ToVersion.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfCol1ToVersion)))); + + getClient(tokenAdminComB).perform(get("/api/authz/authorizations/" + adminOfComBToVersion.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminCol2).perform(get("/api/authz/authorizations/" + adminOfCol2ToVersion.getID())) + .andExpect(status().isNotFound()); + } + +} \ No newline at end of file From 341c0ae640106ceaa51a905d7b28a6b71dac0e93 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Sep 2021 15:20:01 +0200 Subject: [PATCH 0250/1254] added test to verify correct pagination after deleting of version --- .../org/dspace/app/rest/VersionHistoryRestRepositoryIT.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java index 7ac1c7fa45..9edac797d9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java @@ -41,7 +41,6 @@ import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; import org.hamcrest.Matchers; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -413,8 +412,7 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio } @Test - @Ignore - public void deleteVersionXXXXTest() throws Exception { + public void findVersionsOfVersionHistoryCheckPaginationAfterDelitingOfVersionTest() throws Exception { //disable file upload mandatory configurationService.setProperty("webui.submit.upload.required", false); context.turnOffAuthorisationSystem(); From 75b6acc9f310bfbd9ccb4bd8e8af7835472d3be9 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Sep 2021 15:21:41 +0200 Subject: [PATCH 0251/1254] fix pagination bug --- .../java/org/dspace/versioning/VersioningServiceImpl.java | 7 ++++--- .../main/java/org/dspace/versioning/dao/VersionDAO.java | 2 +- .../org/dspace/versioning/dao/impl/VersionDAOImpl.java | 5 +++-- .../org/dspace/versioning/service/VersioningService.java | 5 +++-- .../dspace/app/rest/repository/VersionsLinkRepository.java | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java index a300f6a8b3..b5dee180e5 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java @@ -232,7 +232,8 @@ public class VersioningServiceImpl implements VersioningService { } @Override - public List getVersionsByHistory(Context c, VersionHistory vh, int offset, int limit) throws SQLException { + public List getVersionsByHistoryWithItems(Context c, VersionHistory vh, int offset, int limit) + throws SQLException { return versionDAO.findVersionsWithItems(c, vh, offset, limit); } @@ -263,8 +264,8 @@ public class VersioningServiceImpl implements VersioningService { } @Override - public int countVersionsByHistory(Context context, VersionHistory versionHistory) throws SQLException { - return versionDAO.countVersionsByHistory(context, versionHistory); + public int countVersionsByHistoryWithItem(Context context, VersionHistory versionHistory) throws SQLException { + return versionDAO.countVersionsByHistoryWithItem(context, versionHistory); } } diff --git a/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java b/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java index f041070e1e..21bec23c19 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java +++ b/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java @@ -44,6 +44,6 @@ public interface VersionDAO extends GenericDAO { public int getNextVersionNumber(Context c, VersionHistory vh) throws SQLException; - public int countVersionsByHistory(Context context, VersionHistory versionHistory) throws SQLException; + public int countVersionsByHistoryWithItem(Context context, VersionHistory versionHistory) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java b/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java index e6eac4cb99..0e28e72d07 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/dao/impl/VersionDAOImpl.java @@ -82,9 +82,10 @@ public class VersionDAOImpl extends AbstractHibernateDAO implements Ver } @Override - public int countVersionsByHistory(Context context, VersionHistory versionHistory) throws SQLException { + public int countVersionsByHistoryWithItem(Context context, VersionHistory versionHistory) throws SQLException { Query query = createQuery(context, "SELECT count(*) FROM " + Version.class.getSimpleName() - + " WHERE versionhistory_id = (:versionhistoryId)"); + + " WHERE versionhistory_id = (:versionhistoryId)" + + " AND item_id IS NOT NULL"); query.setParameter("versionhistoryId", versionHistory); return count(query); } diff --git a/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java b/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java index 32f673abf8..dcc87c56aa 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java +++ b/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java @@ -40,7 +40,8 @@ public interface VersioningService { */ List getVersionsByHistory(Context c, VersionHistory vh) throws SQLException; - List getVersionsByHistory(Context c, VersionHistory vh, int offset, int limit) throws SQLException; + List getVersionsByHistoryWithItems(Context c, VersionHistory vh, int offset, int limit) + throws SQLException; /** * Delete a Version @@ -75,6 +76,6 @@ public interface VersioningService { */ public void update(Context context, Version version) throws SQLException; - public int countVersionsByHistory(Context context, VersionHistory versionHistory) throws SQLException; + public int countVersionsByHistoryWithItem(Context context, VersionHistory versionHistory) throws SQLException; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java index 4f317420ed..0c3606fbf7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java @@ -66,10 +66,10 @@ public class VersionsLinkRepository extends AbstractDSpaceRestRepository " couldn't be found"); } Pageable pageable = optionalPageable != null ? optionalPageable : PageRequest.of(0, 20); - List versions = versioningService.getVersionsByHistory(context, versionHistory, + List versions = versioningService.getVersionsByHistoryWithItems(context, versionHistory, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - total = versioningService.countVersionsByHistory(context, versionHistory); + total = versioningService.countVersionsByHistoryWithItem(context, versionHistory); return converter.toRestPage(versions, pageable, total, projection); } } From 72fab650121dbfdbee5c871c43e70ab8ddd721ee Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Mon, 12 Jul 2021 12:25:34 -0400 Subject: [PATCH 0252/1254] w2p-80200 Retain UUIDs of DSpace objects when using packager --- .../content/CheckExistingUUIDGenerator.java | 33 +++++++++++++++ .../java/org/dspace/content/Collection.java | 4 ++ .../dspace/content/CollectionServiceImpl.java | 30 ++++++++++---- .../java/org/dspace/content/Community.java | 5 +++ .../dspace/content/CommunityServiceImpl.java | 39 ++++++++++++++---- .../java/org/dspace/content/DSpaceObject.java | 10 ++++- .../main/java/org/dspace/content/Item.java | 5 +++ .../org/dspace/content/ItemServiceImpl.java | 41 +++++++++++++++++++ .../content/WorkspaceItemServiceImpl.java | 14 ++++++- .../packager/AbstractMETSDisseminator.java | 3 -- .../packager/AbstractMETSIngester.java | 23 ++++++++++- .../dspace/content/packager/PackageUtils.java | 40 +++++++++++++++--- .../content/service/CollectionService.java | 22 ++++++++-- .../content/service/CommunityService.java | 29 +++++++++++++ .../dspace/content/service/ItemService.java | 14 +++++++ .../content/service/WorkspaceItemService.java | 17 ++++++++ 16 files changed, 295 insertions(+), 34 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/CheckExistingUUIDGenerator.java diff --git a/dspace-api/src/main/java/org/dspace/content/CheckExistingUUIDGenerator.java b/dspace-api/src/main/java/org/dspace/content/CheckExistingUUIDGenerator.java new file mode 100644 index 0000000000..c9ed9c1c58 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/CheckExistingUUIDGenerator.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.content; + +import java.io.Serializable; +import java.util.UUID; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.id.UUIDGenerator; + +/** + * Allows DSpaceObjects to provide a pre-determined UUID + * + * @author April Herron + */ +public class CheckExistingUUIDGenerator extends UUIDGenerator { + + @Override + public Serializable generate(SharedSessionContractImplementor session, Object object) { + if (object instanceof DSpaceObject) { + UUID uuid = ((DSpaceObject) object).getPredefinedUUID(); + if (uuid != null) { + return uuid; + } + } + return super.generate(session, object); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index 0658cc2d93..b826487083 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import javax.annotation.Nonnull; import javax.persistence.Cacheable; import javax.persistence.CascadeType; @@ -103,6 +104,9 @@ public class Collection extends DSpaceObject implements DSpaceObjectLegacySuppor protected Collection() { } + protected Collection(UUID uuid) { + this.predefinedUUID = uuid; + } @Override public String getName() { 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 96ba571803..b652fa9b3f 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -129,12 +129,23 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i @Override public Collection create(Context context, Community community, String handle) - throws SQLException, AuthorizeException { + throws SQLException, AuthorizeException { + return create(context, community, handle, null); + } + + @Override + public Collection create(Context context, Community community, + String handle, UUID uuid) throws SQLException, AuthorizeException { if (community == null) { throw new IllegalArgumentException("Community cannot be null when creating a new collection."); } - Collection newCollection = collectionDAO.create(context, new Collection()); + Collection newCollection; + if (uuid != null) { + newCollection = collectionDAO.create(context, new Collection(uuid)); + } else { + newCollection = collectionDAO.create(context, new Collection()); + } //Add our newly created collection to our community, authorization checks occur in THIS method communityService.addCollection(context, community, newCollection); @@ -146,9 +157,10 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i authorizeService.createResourcePolicy(context, newCollection, anonymousGroup, null, Constants.READ, null); // now create the default policies for submitted items authorizeService - .createResourcePolicy(context, newCollection, anonymousGroup, null, Constants.DEFAULT_ITEM_READ, null); + .createResourcePolicy(context, newCollection, anonymousGroup, null, Constants.DEFAULT_ITEM_READ, null); authorizeService - .createResourcePolicy(context, newCollection, anonymousGroup, null, Constants.DEFAULT_BITSTREAM_READ, null); + .createResourcePolicy(context, newCollection, anonymousGroup, null, + Constants.DEFAULT_BITSTREAM_READ, null); collectionDAO.save(context, newCollection); @@ -164,12 +176,12 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i } context.addEvent(new Event(Event.CREATE, Constants.COLLECTION, - newCollection.getID(), newCollection.getHandle(), - getIdentifiers(context, newCollection))); + newCollection.getID(), newCollection.getHandle(), + getIdentifiers(context, newCollection))); log.info(LogHelper.getHeader(context, "create_collection", - "collection_id=" + newCollection.getID()) - + ",handle=" + newCollection.getHandle()); + "collection_id=" + newCollection.getID()) + + ",handle=" + newCollection.getHandle()); return newCollection; } @@ -951,7 +963,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i * Finds all Indexed Collections where the current user has submit rights. If the user is an Admin, * this is all Indexed Collections. Otherwise, it includes those collections where * an indexed "submit" policy lists either the eperson or one of the eperson's groups - * + * * @param context DSpace context * @param discoverQuery * @param community parent community, could be null diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index 810caaf4fd..a1b0ec547d 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import javax.persistence.Cacheable; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -96,6 +97,10 @@ public class Community extends DSpaceObject implements DSpaceObjectLegacySupport } + protected Community(UUID uuid) { + this.predefinedUUID = uuid; + } + void addSubCommunity(Community subCommunity) { subCommunities.add(subCommunity); setModified(); 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 ac60e7788e..b61c33dab8 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -86,13 +86,24 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp @Override public Community create(Community parent, Context context, String handle) throws SQLException, AuthorizeException { + return create(parent, context, handle, null); + } + + @Override + public Community create(Community parent, Context context, String handle, + UUID uuid) throws SQLException, AuthorizeException { if (!(authorizeService.isAdmin(context) || - (parent != null && authorizeService.authorizeActionBoolean(context, parent, Constants.ADD)))) { + (parent != null && authorizeService.authorizeActionBoolean(context, parent, Constants.ADD)))) { throw new AuthorizeException( - "Only administrators can create communities"); + "Only administrators can create communities"); } - Community newCommunity = communityDAO.create(context, new Community()); + Community newCommunity; + if (uuid != null) { + newCommunity = communityDAO.create(context, new Community(uuid)); + } else { + newCommunity = communityDAO.create(context, new Community()); + } if (parent != null) { parent.addSubCommunity(newCommunity); @@ -129,8 +140,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp } log.info(LogHelper.getHeader(context, "create_community", - "community_id=" + newCommunity.getID()) - + ",handle=" + newCommunity.getHandle()); + "community_id=" + newCommunity.getID()) + + ",handle=" + newCommunity.getHandle()); return newCommunity; } @@ -383,17 +394,29 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp @Override public Community createSubcommunity(Context context, Community parentCommunity) - throws SQLException, AuthorizeException { + throws SQLException, AuthorizeException { return createSubcommunity(context, parentCommunity, null); } + @Override public Community createSubcommunity(Context context, Community parentCommunity, String handle) - throws SQLException, AuthorizeException { + throws SQLException, AuthorizeException { + return createSubcommunity(context, parentCommunity, handle, null); + } + + @Override + public Community createSubcommunity(Context context, Community parentCommunity, String handle, + UUID uuid) throws SQLException, AuthorizeException { // Check authorisation authorizeService.authorizeAction(context, parentCommunity, Constants.ADD); - Community c = create(parentCommunity, context, handle); + Community c; + if (uuid != null) { + c = create(parentCommunity, context, handle, uuid); + } else { + c = create(parentCommunity, context, handle); + } addSubcommunity(context, parentCommunity, c); return c; diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java index b740a6b82d..9829fa9003 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java @@ -38,8 +38,8 @@ import org.hibernate.annotations.GenericGenerator; @Table(name = "dspaceobject") public abstract class DSpaceObject implements Serializable, ReloadableEntity { @Id - @GeneratedValue(generator = "system-uuid") - @GenericGenerator(name = "system-uuid", strategy = "uuid2") + @GeneratedValue(generator = "check-existing-uuid") + @GenericGenerator(name = "check-existing-uuid", strategy = "org.dspace.content.CheckExistingUUIDGenerator") @Column(name = "uuid", unique = true, nullable = false, insertable = true, updatable = false) protected java.util.UUID id; @@ -76,6 +76,12 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity implements It return item; } + @Override + public Item create(Context context, WorkspaceItem workspaceItem, + UUID uuid) throws SQLException, AuthorizeException { + if (workspaceItem.getItem() != null) { + throw new IllegalArgumentException( + "Attempting to create an item for a workspace item that already contains an item"); + } + Item item = createItem(context, uuid); + workspaceItem.setItem(item); + + + log.info(LogManager.getHeader(context, "create_item", "item_id=" + + item.getID())); + + return item; + } + @Override public Item createTemplateItem(Context context, Collection collection) throws SQLException, AuthorizeException { if (collection == null || collection.getTemplateItem() != null) { @@ -418,6 +435,30 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It return bitstreamList; } + protected Item createItem(Context context, UUID uuid) throws SQLException, AuthorizeException { + Item item; + if (uuid != null) { + item = itemDAO.create(context, new Item(uuid)); + } else { + item = itemDAO.create(context, new Item()); + } + // set discoverable to true (default) + item.setDiscoverable(true); + + // Call update to give the item a last modified date. OK this isn't + // amazingly efficient but creates don't happen that often. + context.turnOffAuthorisationSystem(); + update(context, item); + context.restoreAuthSystemState(); + + context.addEvent(new Event(Event.CREATE, Constants.ITEM, item.getID(), + null, getIdentifiers(context, item))); + + log.info(LogManager.getHeader(context, "create_item", "item_id=" + item.getID())); + + return item; + } + protected Item createItem(Context context) throws SQLException, AuthorizeException { Item item = itemDAO.create(context, new Item()); // set discoverable to true (default) 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 7675d298d6..d1a761268c 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -12,6 +12,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReaderException; @@ -80,6 +81,12 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { @Override public WorkspaceItem create(Context context, Collection collection, boolean template) + throws AuthorizeException, SQLException { + return create(context, collection, null, template); + } + + @Override + public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template) throws AuthorizeException, SQLException { // Check the user has permission to ADD to the collection authorizeService.authorizeAction(context, collection, Constants.ADD); @@ -89,7 +96,12 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { // Create an item - Item item = itemService.create(context, workspaceItem); + Item item; + if (uuid != null) { + item = itemService.create(context, workspaceItem, uuid); + } else { + item = itemService.create(context, workspaceItem); + } item.setSubmitter(context.getCurrentUser()); // Now create the policies for the submitter to modify item and contents diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java index 2f90dae354..471b9ba27c 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java @@ -777,9 +777,6 @@ public abstract class AbstractMETSDisseminator Mets mets = new Mets(); String identifier = "DB-ID-" + dso.getID(); - if (dso.getHandle() != null) { - identifier = dso.getHandle().replace('/', '-'); - } // this ID should be globally unique (format: DSpace_[objType]_[handle with slash replaced with a dash]) mets.setID("DSpace_" + Constants.typeText[dso.getType()] + "_" + identifier); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 67e20581ef..9a7fffdec5 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -16,6 +16,7 @@ import java.net.URLConnection; import java.sql.SQLException; import java.util.Iterator; import java.util.List; +import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -409,6 +410,7 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { // get handle from manifest handle = getObjectHandle(manifest); } + UUID uuid = getObjectID(manifest); // -- Step 2 -- // Create our DSpace Object based on info parsed from manifest, and @@ -416,7 +418,7 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { DSpaceObject dso; try { dso = PackageUtils.createDSpaceObject(context, parent, - type, handle, params); + type, handle, uuid, params); } catch (SQLException sqle) { throw new PackageValidationException("Exception while ingesting " + pkgFile.getPath(), sqle); @@ -727,7 +729,6 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { // retrieve path/name of file in manifest String path = METSManifest.getFileName(mfile); - // extract the file input stream from package (or retrieve // externally, if it is an externally referenced file) InputStream fileStream = getFileInputStream(pkgFile, params, path); @@ -1506,4 +1507,22 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester { */ public abstract String getConfigurationName(); + public UUID getObjectID(METSManifest manifest) + throws PackageValidationException { + Element mets = manifest.getMets(); + String idStr = mets.getAttributeValue("ID"); + if (idStr == null || idStr.length() == 0) { + throw new PackageValidationException("Manifest is missing the required mets@ID attribute."); + } + if (idStr.contains("DB-ID-")) { + idStr = idStr.substring(idStr.lastIndexOf("DB-ID-") + 6, idStr.length()); + } + try { + return UUID.fromString(idStr); + } catch (IllegalArgumentException ignored) { + //do nothing + } + return null; + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java index c127b48af9..9e7d870076 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -447,6 +448,7 @@ public class PackageUtils { * @param parent Parent Object * @param type Type of new Object * @param handle Handle of new Object (may be null) + * @param uuid * @param params Properties-style list of options (interpreted by each packager). * @return newly created DSpace Object (or null) * @throws AuthorizeException if authorization error @@ -454,29 +456,55 @@ public class PackageUtils { * @throws IOException if IO error */ public static DSpaceObject createDSpaceObject(Context context, DSpaceObject parent, int type, String handle, - PackageParameters params) + UUID uuid, PackageParameters params) throws AuthorizeException, SQLException, IOException { DSpaceObject dso = null; switch (type) { case Constants.COLLECTION: - dso = collectionService.create(context, (Community) parent, handle); + Collection collection = collectionService.find(context, uuid); + if (collection != null) { + dso = collectionService.create(context, (Community) parent, handle); + } else { + dso = collectionService.create(context, (Community) parent, handle, uuid); + + } return dso; case Constants.COMMUNITY: // top-level community? if (parent == null || parent.getType() == Constants.SITE) { - dso = communityService.create(null, context, handle); + Community community = communityService.find(context, uuid); + if (community != null) { + dso = communityService.create(null, context, handle); + } else { + dso = communityService.create(null, context, handle, uuid); + } } else { - dso = communityService.createSubcommunity(context, ((Community) parent), handle); + Community community = communityService.find(context, uuid); + if (community != null) { + dso = communityService.createSubcommunity(context, ((Community) parent), handle); + } else { + dso = communityService.createSubcommunity(context, ((Community) parent), handle, uuid); + } } return dso; case Constants.ITEM: //Initialize a WorkspaceItem //(Note: Handle is not set until item is finished) - WorkspaceItem wsi = workspaceItemService - .create(context, (Collection) parent, params.useCollectionTemplate()); + Item item = itemService.find(context, uuid); + if (item != null) { + return item; + } + + WorkspaceItem wsi = null; + if (!params.replaceModeEnabled()) { + wsi = workspaceItemService.create(context, (Collection)parent, params.useCollectionTemplate()); + } else { + wsi = workspaceItemService.create(context, (Collection)parent, + uuid, params.useCollectionTemplate()); + } // Please note that we are returning an Item which is *NOT* yet in the Archive, // and doesn't yet have a handle assigned. 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 67d45939d5..2580e74b99 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 @@ -12,6 +12,7 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.List; import java.util.Map; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; @@ -60,6 +61,21 @@ public interface CollectionService public Collection create(Context context, Community community, String handle) throws SQLException, AuthorizeException; + /** + * Create a new collection with the supplied handle and ID. + * Once created the collection is added to the given community + * + * @param context DSpace context object + * @param community DSpace Community (parent) + * @param handle the pre-determined Handle to assign to the new collection + * @param uuid the pre-determined UUID to assign to the new collection + * @return the newly created collection + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public Collection create(Context context, Community community, String handle, UUID uuid) throws SQLException, + AuthorizeException; + /** * Get all collections in the system. These are alphabetically sorted by * collection name. @@ -308,7 +324,7 @@ public interface CollectionService throws java.sql.SQLException; /** - * + * * @param context DSpace Context * @param group EPerson Group * @return the collection, if any, that has the specified group as administrators or submitters @@ -349,7 +365,7 @@ public interface CollectionService * NOTE: for better performance, this method retrieves its results from an * index (cache) and does not query the database directly. * This means that results may be stale or outdated until DS-4524 is resolved" - * + * * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement * an autosuggest feature over the collection name @@ -369,7 +385,7 @@ public interface CollectionService * NOTE: for better performance, this method retrieves its results from an index (cache) * and does not query the database directly. * This means that results may be stale or outdated until DS-4524 is resolved." - * + * * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement * an autosuggest feature over the collection name diff --git a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java index 54512bb9f5..e7b6212665 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; @@ -53,6 +54,20 @@ public interface CommunityService extends DSpaceObjectService, DSpace public Community create(Community parent, Context context, String handle) throws SQLException, AuthorizeException; + /** + * Create a new top-level community, with a new ID. + * + * @param parent parent community + * @param context DSpace context object + * @param handle the pre-determined Handle to assign to the new community + * @param uuid the pre-determined uuid to assign to the new community + * @return the newly created community + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public Community create(Community parent, Context context, + String handle, UUID uuid) throws SQLException, AuthorizeException; + /** * Get a list of all communities in the system. These are alphabetically @@ -202,6 +217,20 @@ public interface CommunityService extends DSpaceObjectService, DSpace public Community createSubcommunity(Context context, Community parentCommunity, String handle) throws SQLException, AuthorizeException; + /** + * Create a new sub-community within this community. + * + * @param context context + * @param handle the pre-determined Handle to assign to the new community + * @param parentCommunity parent community + * @param uuid the pre-determined UUID to assign to the new community + * @return the new community + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public Community createSubcommunity(Context context, Community parentCommunity, String handle, UUID uuid) + throws SQLException, AuthorizeException; + /** * Add an existing community as a subcommunity to the community * 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 2a38488f7a..3353eb9dec 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 @@ -55,6 +55,20 @@ public interface ItemService */ public Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException; + /** + * Create a new item, with a provided ID. This method is not public, + * since items need to be created as workspace items. Authorisation is the + * responsibility of the caller. + * + * @param context DSpace context object + * @param workspaceItem in progress workspace item + * @param uuid the pre-determined UUID to assign to the new item + * @return the newly created item + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public Item create(Context context, WorkspaceItem workspaceItem, UUID uuid) throws SQLException, AuthorizeException; + /** * Create an empty template item for this collection. If one already exists, * no action is taken. Caution: Make sure you call update on 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 3ee381706c..8f572f6108 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 @@ -11,6 +11,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; import java.util.Map; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; @@ -55,6 +56,22 @@ public interface WorkspaceItemService extends InProgressSubmissionServicetrue, the workspace item starts as a copy + * of the collection's template item + * @return the newly created workspace item + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public WorkspaceItem create(Context context, Collection collection, UUID uuid, boolean template) + throws AuthorizeException, SQLException; + public WorkspaceItem create(Context c, WorkflowItem wfi) throws SQLException, AuthorizeException; From 80211961c0ecc6f5206f3e0483e47e17bf381265 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Thu, 15 Jul 2021 14:32:16 -0400 Subject: [PATCH 0253/1254] w2p-80200 Begin IT for packager export/import with UUID support --- .../org/dspace/app/packager/PackagerIT.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java new file mode 100644 index 0000000000..12044cf941 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.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.packager; + +import java.io.File; + +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + + +// See CsvImportIT for other examples involving rels +public class PackagerIT extends AbstractEntityIntegrationTest { + + @Autowired + private ItemService itemService; + + @Test + public void packagerExportUUIDTest() 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(); + + // Create a new Publication (which is an Article) + Item article = ItemBuilder.createItem(context, col1) + .withTitle("Article") + .withIssueDate("2017-10-17") + .withEntityType("Publication") + .build(); + + File tempFile = File.createTempFile("packagerExportTest", "zip"); + try { + performExportScript(article.getHandle(), tempFile); + // TODO: verify the file has the uuid in the right place + } finally { + tempFile.delete(); + } + } + + @Test + public void packagerImportUUIDTest() { + + } + + private void performExportScript(String handle, File outputFile) throws Exception { + runDSpaceScript("packager", "-d", "-e", "admin@email.com", "-i", handle, "aip", outputFile.getPath()); + } +} From f0ae18645da632026dd61f647132542cec9b4772 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Fri, 16 Jul 2021 09:41:03 -0400 Subject: [PATCH 0254/1254] Test file for export and import of packager --- .../org/dspace/app/packager/PackagerIT.java | 107 ++++++++++++++---- 1 file changed, 87 insertions(+), 20 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java index 12044cf941..3b08fa3e80 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -7,18 +7,30 @@ */ package org.dspace.app.packager; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.io.File; +import java.io.IOException; +import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; -import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.crosswalk.MetadataValidationException; +import org.dspace.content.packager.METSManifest; import org.dspace.content.service.ItemService; +import org.jdom.Element; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; - +import org.springframework.core.annotation.Order; // See CsvImportIT for other examples involving rels public class PackagerIT extends AbstractEntityIntegrationTest { @@ -27,38 +39,93 @@ public class PackagerIT extends AbstractEntityIntegrationTest { private ItemService itemService; @Test + @Order(1) public void packagerExportUUIDTest() 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(); + Community child1 = null; + Collection col1 = null; + Item article = null; + createTemplate(child1, col1, article); - // Create a new Publication (which is an Article) - Item article = ItemBuilder.createItem(context, col1) - .withTitle("Article") - .withIssueDate("2017-10-17") - .withEntityType("Publication") - .build(); - - File tempFile = File.createTempFile("packagerExportTest", "zip"); + File tempFile = File.createTempFile("packagerExportTest", ".zip"); try { performExportScript(article.getHandle(), tempFile); - // TODO: verify the file has the uuid in the right place + assertTrue(tempFile.length() > 0); + String idStr = getID(tempFile); + assertEquals(idStr, article.getID().toString()); + } catch (Exception e) { + throw new Exception(e); } finally { tempFile.delete(); } } @Test - public void packagerImportUUIDTest() { + @Order(2) + public void packagerImportUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community child1 = null; + Collection col1 = null; + Item article = null; + createTemplate(child1, col1, article); + File tempFile = File.createTempFile("packagerExportTest", ".zip"); + try { + performExportScript(article.getHandle(), tempFile); + String idStr = getID(tempFile); + itemService.delete(context, article); + performImportScript(tempFile); + System.out.println(idStr); + Item item = itemService.find(context, UUID.fromString(idStr)); + assertNotNull(item); + } catch (Exception e) { + throw new Exception(e); + } finally { + tempFile.delete(); + } } + private void createTemplate(Community child1, Collection col1, Item article) { + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + // Create a new Publication (which is an Article) + article = ItemBuilder.createItem(context, col1) + .withTitle("Article") + .withIssueDate("2017-10-17") + .withEntityType("Publication") + .build(); + } + + private String getID(File tempFile) throws IOException, MetadataValidationException { + METSManifest manifest = null; + System.out.println(tempFile.getAbsolutePath()); + ZipFile zip = new ZipFile(tempFile); + ZipEntry manifestEntry = zip.getEntry(METSManifest.MANIFEST_FILE); + if (manifestEntry != null) { + // parse the manifest and sanity-check it. + manifest = METSManifest.create(zip.getInputStream(manifestEntry), + false, "AIP"); + } + Element mets = manifest.getMets(); + String idStr = mets.getAttributeValue("ID"); + if (idStr.contains("DB-ID-")) { + idStr = idStr.substring(idStr.lastIndexOf("DB-ID-") + 6, idStr.length()); + } + return idStr; + } + + private void performExportScript(String handle, File outputFile) throws Exception { - runDSpaceScript("packager", "-d", "-e", "admin@email.com", "-i", handle, "aip", outputFile.getPath()); + runDSpaceScript("packager", "-d", "-e", "admin@email.com", "-i", handle, "-t", "AIP", outputFile.getPath()); + } + + private void performImportScript(File outputFile) throws Exception { + runDSpaceScript("packager", "-r", "-f", "-u", "-e", "admin@email.com", "-t", "AIP", outputFile.getPath()); } } From cc63bbe02df9d5b28483e338335c49be37a71e8e Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Fri, 16 Jul 2021 09:53:21 -0400 Subject: [PATCH 0255/1254] quick fix for Item not being initialized --- .../src/test/java/org/dspace/app/packager/PackagerIT.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java index 3b08fa3e80..11e9462b85 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -45,7 +45,7 @@ public class PackagerIT extends AbstractEntityIntegrationTest { Community child1 = null; Collection col1 = null; Item article = null; - createTemplate(child1, col1, article); + article = createTemplate(child1, col1, article); File tempFile = File.createTempFile("packagerExportTest", ".zip"); try { @@ -67,7 +67,8 @@ public class PackagerIT extends AbstractEntityIntegrationTest { Community child1 = null; Collection col1 = null; Item article = null; - createTemplate(child1, col1, article); + article = createTemplate(child1, col1, article); + File tempFile = File.createTempFile("packagerExportTest", ".zip"); try { @@ -85,7 +86,7 @@ public class PackagerIT extends AbstractEntityIntegrationTest { } } - private void createTemplate(Community child1, Collection col1, Item article) { + private Item createTemplate(Community child1, Collection col1, Item article) { parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); @@ -100,6 +101,7 @@ public class PackagerIT extends AbstractEntityIntegrationTest { .withIssueDate("2017-10-17") .withEntityType("Publication") .build(); + return article; } private String getID(File tempFile) throws IOException, MetadataValidationException { From 22c9b769508ec2890ca0728f55e3fcc080c04731 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Fri, 16 Jul 2021 16:20:38 -0400 Subject: [PATCH 0256/1254] move to dspace-api --- .../org/dspace/app/packager/PackagerIT.java | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) rename {dspace-server-webapp => dspace-api}/src/test/java/org/dspace/app/packager/PackagerIT.java (76%) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java similarity index 76% rename from dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java rename to dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 11e9462b85..432bc1547d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -17,7 +17,7 @@ import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.ItemBuilder; @@ -25,27 +25,21 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.crosswalk.MetadataValidationException; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.packager.METSManifest; import org.dspace.content.service.ItemService; import org.jdom.Element; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.annotation.Order; // See CsvImportIT for other examples involving rels -public class PackagerIT extends AbstractEntityIntegrationTest { +public class PackagerIT extends AbstractIntegrationTestWithDatabase { - @Autowired - private ItemService itemService; + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); @Test - @Order(1) public void packagerExportUUIDTest() throws Exception { context.turnOffAuthorisationSystem(); - Community child1 = null; - Collection col1 = null; - Item article = null; - article = createTemplate(child1, col1, article); + Item article = createTemplate(); File tempFile = File.createTempFile("packagerExportTest", ".zip"); try { @@ -61,13 +55,9 @@ public class PackagerIT extends AbstractEntityIntegrationTest { } @Test - @Order(2) public void packagerImportUUIDTest() throws Exception { context.turnOffAuthorisationSystem(); - Community child1 = null; - Collection col1 = null; - Item article = null; - article = createTemplate(child1, col1, article); + Item article = createTemplate(); File tempFile = File.createTempFile("packagerExportTest", ".zip"); @@ -76,7 +66,6 @@ public class PackagerIT extends AbstractEntityIntegrationTest { String idStr = getID(tempFile); itemService.delete(context, article); performImportScript(tempFile); - System.out.println(idStr); Item item = itemService.find(context, UUID.fromString(idStr)); assertNotNull(item); } catch (Exception e) { @@ -86,27 +75,25 @@ public class PackagerIT extends AbstractEntityIntegrationTest { } } - private Item createTemplate(Community child1, Collection col1, Item article) { + private Item createTemplate() { parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); - child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) .withName("Sub Community") .build(); - col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); // Create a new Publication (which is an Article) - article = ItemBuilder.createItem(context, col1) + return ItemBuilder.createItem(context, col1) .withTitle("Article") .withIssueDate("2017-10-17") .withEntityType("Publication") .build(); - return article; } private String getID(File tempFile) throws IOException, MetadataValidationException { METSManifest manifest = null; - System.out.println(tempFile.getAbsolutePath()); ZipFile zip = new ZipFile(tempFile); ZipEntry manifestEntry = zip.getEntry(METSManifest.MANIFEST_FILE); if (manifestEntry != null) { @@ -124,10 +111,12 @@ public class PackagerIT extends AbstractEntityIntegrationTest { private void performExportScript(String handle, File outputFile) throws Exception { - runDSpaceScript("packager", "-d", "-e", "admin@email.com", "-i", handle, "-t", "AIP", outputFile.getPath()); + runDSpaceScript("packager", "-d", "-e", "admin@email.com", "-i", handle, "-t", + "AIP", outputFile.getPath()); } private void performImportScript(File outputFile) throws Exception { - runDSpaceScript("packager", "-r", "-f", "-u", "-e", "admin@email.com", "-t", "AIP", outputFile.getPath()); + runDSpaceScript("packager", "-r", "-f", "-u", "-e", "admin@email.com", "-t", + "AIP", outputFile.getPath()); } } From b24da56ca2fd6ad515a0d65092bd813a4f83cc81 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Mon, 19 Jul 2021 11:20:10 -0400 Subject: [PATCH 0257/1254] remove left over comment --- dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 432bc1547d..3ae92730af 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -31,7 +31,6 @@ import org.dspace.content.service.ItemService; import org.jdom.Element; import org.junit.Test; -// See CsvImportIT for other examples involving rels public class PackagerIT extends AbstractIntegrationTestWithDatabase { private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); From cdeb22ed7beb7dd1086100ed76a6e4f436131003 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Mon, 19 Jul 2021 11:25:18 -0400 Subject: [PATCH 0258/1254] Make tests more concise and remove catch blocks --- .../org/dspace/app/packager/PackagerIT.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 3ae92730af..11fe4119e2 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -34,20 +34,21 @@ import org.junit.Test; public class PackagerIT extends AbstractIntegrationTestWithDatabase { private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected Community child1; + protected Collection col1; + protected Item article; + File tempFile; @Test public void packagerExportUUIDTest() throws Exception { context.turnOffAuthorisationSystem(); - Item article = createTemplate(); - - File tempFile = File.createTempFile("packagerExportTest", ".zip"); + createTemplate(); + createFiles(); try { performExportScript(article.getHandle(), tempFile); assertTrue(tempFile.length() > 0); - String idStr = getID(tempFile); + String idStr = getID(); assertEquals(idStr, article.getID().toString()); - } catch (Exception e) { - throw new Exception(e); } finally { tempFile.delete(); } @@ -56,42 +57,43 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { @Test public void packagerImportUUIDTest() throws Exception { context.turnOffAuthorisationSystem(); - Item article = createTemplate(); - - - File tempFile = File.createTempFile("packagerExportTest", ".zip"); + createTemplate(); + createFiles(); try { performExportScript(article.getHandle(), tempFile); - String idStr = getID(tempFile); + String idStr = getID(); itemService.delete(context, article); performImportScript(tempFile); Item item = itemService.find(context, UUID.fromString(idStr)); assertNotNull(item); - } catch (Exception e) { - throw new Exception(e); } finally { tempFile.delete(); } } - private Item createTemplate() { + protected void createTemplate() { parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) .withName("Sub Community") .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); // Create a new Publication (which is an Article) - return ItemBuilder.createItem(context, col1) + article = ItemBuilder.createItem(context, col1) .withTitle("Article") .withIssueDate("2017-10-17") .withEntityType("Publication") .build(); } - private String getID(File tempFile) throws IOException, MetadataValidationException { + protected void createFiles() throws IOException { + tempFile = File.createTempFile("packagerExportTest", ".zip"); + } + + private String getID() throws IOException, MetadataValidationException { + //this method gets the UUID from the mets file thats stored in the attribute element METSManifest manifest = null; ZipFile zip = new ZipFile(tempFile); ZipEntry manifestEntry = zip.getEntry(METSManifest.MANIFEST_FILE); From e1f798fd827d741b766d1eebe47a1d31ddd8f173 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Mon, 30 Aug 2021 14:38:25 -0400 Subject: [PATCH 0259/1254] w2p-80200 Add new tests for each dspace object, cleaned up java docs and authrorization inside of create --- .../java/org/dspace/content/Collection.java | 7 +++ .../java/org/dspace/content/Community.java | 6 ++ .../java/org/dspace/content/DSpaceObject.java | 7 ++- .../main/java/org/dspace/content/Item.java | 6 ++ .../org/dspace/content/ItemServiceImpl.java | 2 + ...ator.java => PredefinedUUIDGenerator.java} | 2 +- .../dspace/content/service/ItemService.java | 10 ++- .../org/dspace/app/packager/PackagerIT.java | 63 +++++++++++++++++++ dspace/modules/dspace-replicate | 1 + 9 files changed, 95 insertions(+), 9 deletions(-) rename dspace-api/src/main/java/org/dspace/content/{CheckExistingUUIDGenerator.java => PredefinedUUIDGenerator.java} (93%) create mode 160000 dspace/modules/dspace-replicate diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index b826487083..ffec3b45cc 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -104,6 +104,13 @@ public class Collection extends DSpaceObject implements DSpaceObjectLegacySuppor protected Collection() { } + + /** + * Takes a pre-determined UUID to be passed to the object to allow for the + * restoration of previously defined UUID's. + * + * @param uuid Takes a uuid to be passed to the Pre-Defined UUID Generator + */ protected Collection(UUID uuid) { this.predefinedUUID = uuid; } diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index a1b0ec547d..d05ab50d93 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -97,6 +97,12 @@ public class Community extends DSpaceObject implements DSpaceObjectLegacySupport } + /** + * Takes a pre-determined UUID to be passed to the object to allow for the + * restoration of previously defined UUID's. + * + * @param uuid Takes a uuid to be passed to the Pre-Defined UUID Generator + */ protected Community(UUID uuid) { this.predefinedUUID = uuid; } diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java index 9829fa9003..fccf51af01 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java @@ -38,8 +38,8 @@ import org.hibernate.annotations.GenericGenerator; @Table(name = "dspaceobject") public abstract class DSpaceObject implements Serializable, ReloadableEntity { @Id - @GeneratedValue(generator = "check-existing-uuid") - @GenericGenerator(name = "check-existing-uuid", strategy = "org.dspace.content.CheckExistingUUIDGenerator") + @GeneratedValue(generator = "predefined-uuid") + @GenericGenerator(name = "predefined-uuid", strategy = "org.dspace.content.PredefinedUUIDGenerator") @Column(name = "uuid", unique = true, nullable = false, insertable = true, updatable = false) protected java.util.UUID id; @@ -76,6 +76,9 @@ public abstract class DSpaceObject implements Serializable, ReloadableEntity implements It @Override public Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException { + authorizeService.authorizeAction(context, workspaceItem.getCollection(), Constants.ADD); if (workspaceItem.getItem() != null) { throw new IllegalArgumentException( "Attempting to create an item for a workspace item that already contains an item"); @@ -193,6 +194,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Override public Item create(Context context, WorkspaceItem workspaceItem, UUID uuid) throws SQLException, AuthorizeException { + authorizeService.authorizeAction(context, workspaceItem.getCollection(), Constants.ADD); if (workspaceItem.getItem() != null) { throw new IllegalArgumentException( "Attempting to create an item for a workspace item that already contains an item"); diff --git a/dspace-api/src/main/java/org/dspace/content/CheckExistingUUIDGenerator.java b/dspace-api/src/main/java/org/dspace/content/PredefinedUUIDGenerator.java similarity index 93% rename from dspace-api/src/main/java/org/dspace/content/CheckExistingUUIDGenerator.java rename to dspace-api/src/main/java/org/dspace/content/PredefinedUUIDGenerator.java index c9ed9c1c58..aa4a8ea542 100644 --- a/dspace-api/src/main/java/org/dspace/content/CheckExistingUUIDGenerator.java +++ b/dspace-api/src/main/java/org/dspace/content/PredefinedUUIDGenerator.java @@ -18,7 +18,7 @@ import org.hibernate.id.UUIDGenerator; * * @author April Herron */ -public class CheckExistingUUIDGenerator extends UUIDGenerator { +public class PredefinedUUIDGenerator extends UUIDGenerator { @Override public Serializable generate(SharedSessionContractImplementor session, Object object) { 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 3353eb9dec..167b9dbb79 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 @@ -43,9 +43,8 @@ public interface ItemService public Thumbnail getThumbnail(Context context, Item item, boolean requireOriginal) throws SQLException; /** - * Create a new item, with a new internal ID. This method is not public, - * since items need to be created as workspace items. Authorisation is the - * responsibility of the caller. + * Create a new item, with a new internal ID. Authorization is done + * inside of this method. * * @param context DSpace context object * @param workspaceItem in progress workspace item @@ -56,9 +55,8 @@ public interface ItemService public Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException; /** - * Create a new item, with a provided ID. This method is not public, - * since items need to be created as workspace items. Authorisation is the - * responsibility of the caller. + * Create a new item, with a provided ID. Authorisation is done + * inside of this method. * * @param context DSpace context object * @param workspaceItem in progress workspace item diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 11fe4119e2..f09b30fb31 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -13,10 +13,12 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.util.Iterator; import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import com.google.common.collect.Iterators; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -27,13 +29,20 @@ import org.dspace.content.Item; import org.dspace.content.crosswalk.MetadataValidationException; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.packager.METSManifest; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.jdom.Element; import org.junit.Test; public class PackagerIT extends AbstractIntegrationTestWithDatabase { private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + private CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + protected ConfigurationService configService = DSpaceServicesFactory.getInstance().getConfigurationService(); protected Community child1; protected Collection col1; protected Item article; @@ -60,6 +69,7 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { createTemplate(); createFiles(); try { + //Item performExportScript(article.getHandle(), tempFile); String idStr = getID(); itemService.delete(context, article); @@ -71,6 +81,59 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { } } + @Test + public void packagerImportColUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + createTemplate(); + createFiles(); + configService.setProperty("upload.temp.dir",tempFile.getParent()); + try { + performExportScript(col1.getHandle(), tempFile); + String idStr = getID(); + collectionService.delete(context, col1); + performImportScript(tempFile); + Collection collection = collectionService.find(context, UUID.fromString(idStr)); + assertNotNull(collection); + } finally { + tempFile.delete(); + } + } + + @Test + public void packagerImportComUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + createTemplate(); + createFiles(); + configService.setProperty("upload.temp.dir",tempFile.getParent()); + try { + //Community + performExportScript(child1.getHandle(), tempFile); + String idStr = getID(); + communityService.delete(context, child1); + performImportScript(tempFile); + Community community = communityService.find(context, UUID.fromString(idStr)); + assertNotNull(community); + } finally { + tempFile.delete(); + } + } + + @Test + public void packagerUUIDAlreadyExistTest() throws Exception { + context.turnOffAuthorisationSystem(); + createTemplate(); + createFiles(); + try { + //Item should be overwritten if UUID already Exists + performExportScript(article.getHandle(), tempFile); + performImportScript(tempFile); + Iterator items = itemService.findByCollection(context, col1); + assertEquals(1, Iterators.size(items)); + } finally { + tempFile.delete(); + } + } + protected void createTemplate() { parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") diff --git a/dspace/modules/dspace-replicate b/dspace/modules/dspace-replicate new file mode 160000 index 0000000000..5c19e3e515 --- /dev/null +++ b/dspace/modules/dspace-replicate @@ -0,0 +1 @@ +Subproject commit 5c19e3e515960d714ead13a2013103135ea0bf3a From 85cbd4b31f4d8b32d4dbb07e775a54cc152c23b5 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Mon, 30 Aug 2021 14:42:56 -0400 Subject: [PATCH 0260/1254] w2p-80200 submodule accidentally pushed --- dspace/modules/dspace-replicate | 1 - 1 file changed, 1 deletion(-) delete mode 160000 dspace/modules/dspace-replicate diff --git a/dspace/modules/dspace-replicate b/dspace/modules/dspace-replicate deleted file mode 160000 index 5c19e3e515..0000000000 --- a/dspace/modules/dspace-replicate +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5c19e3e515960d714ead13a2013103135ea0bf3a From 829fc7d112b6fd91fac73faeab682ef6ed64b184 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Tue, 7 Sep 2021 16:33:32 -0400 Subject: [PATCH 0261/1254] w2p-80200 Tests for no force export and fix to spy on authorize service --- .../dspace/content/CommunityServiceImpl.java | 7 ++-- .../org/dspace/content/ItemServiceImpl.java | 26 ++++++-------- .../org/dspace/app/packager/PackagerIT.java | 36 +++++++++++++++++++ .../org/dspace/content/WorkspaceItemTest.java | 11 +++++- 4 files changed, 58 insertions(+), 22 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 b61c33dab8..53cfb34282 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -412,11 +412,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp authorizeService.authorizeAction(context, parentCommunity, Constants.ADD); Community c; - if (uuid != null) { - c = create(parentCommunity, context, handle, uuid); - } else { - c = create(parentCommunity, context, handle); - } + c = create(parentCommunity, context, handle, uuid); + addSubcommunity(context, parentCommunity, c); return c; 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 b435df4d4e..2244d4b149 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -176,34 +176,28 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Override public Item create(Context context, WorkspaceItem workspaceItem) throws SQLException, AuthorizeException { - authorizeService.authorizeAction(context, workspaceItem.getCollection(), Constants.ADD); - if (workspaceItem.getItem() != null) { - throw new IllegalArgumentException( - "Attempting to create an item for a workspace item that already contains an item"); - } - Item item = createItem(context); - workspaceItem.setItem(item); - - - log.info(LogHelper.getHeader(context, "create_item", "item_id=" - + item.getID())); - - return item; + return create(context, workspaceItem, null); } @Override public Item create(Context context, WorkspaceItem workspaceItem, UUID uuid) throws SQLException, AuthorizeException { - authorizeService.authorizeAction(context, workspaceItem.getCollection(), Constants.ADD); + Collection collection = workspaceItem.getCollection(); + authorizeService.authorizeAction(context, collection, Constants.ADD); if (workspaceItem.getItem() != null) { throw new IllegalArgumentException( "Attempting to create an item for a workspace item that already contains an item"); } - Item item = createItem(context, uuid); + Item item = null; + if (uuid != null) { + item = createItem(context, uuid); + } else { + item = createItem(context); + } workspaceItem.setItem(item); - log.info(LogManager.getHeader(context, "create_item", "item_id=" + log.info(LogHelper.getHeader(context, "create_item", "item_id=" + item.getID())); return item; diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index f09b30fb31..037404ef40 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -8,7 +8,9 @@ package org.dspace.app.packager; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.File; @@ -26,12 +28,15 @@ import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; import org.dspace.content.crosswalk.MetadataValidationException; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.packager.METSManifest; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; +import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.jdom.Element; @@ -42,6 +47,9 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); private CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + private WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + protected static final InstallItemService installItemService = ContentServiceFactory.getInstance() + .getInstallItemService(); protected ConfigurationService configService = DSpaceServicesFactory.getInstance().getConfigurationService(); protected Community child1; protected Collection col1; @@ -134,6 +142,29 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { } } + @Test + public void packagerUUIDAlreadyExistWithoutForceTest() throws Exception { + context.turnOffAuthorisationSystem(); + createTemplate(); + createFiles(); + try { + //should fail to restore the item because the uuid already exists. + performExportScript(article.getHandle(), tempFile); + UUID id = article.getID(); + itemService.delete(context, article); + WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, false); + installItemService.installItem(context, workspaceItem, "123456789/100"); + performImportNoForceScript(tempFile); + Iterator items = itemService.findByCollection(context, col1); + Item testItem = items.next(); + assertFalse(items.hasNext()); //check to make sure there is only 1 item + assertSame("123456789/100", testItem.getHandle()); //check to make sure the item wasn't overwritten as + // it would have the old handle. + } finally { + tempFile.delete(); + } + } + protected void createTemplate() { parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -179,6 +210,11 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { "AIP", outputFile.getPath()); } + private void performImportNoForceScript(File outputFile) throws Exception { + runDSpaceScript("packager", "-r", "-u", "-e", "admin@email.com", "-t", + "AIP", outputFile.getPath()); + } + private void performImportScript(File outputFile) throws Exception { runDSpaceScript("packager", "-r", "-f", "-u", "-e", "admin@email.com", "-t", "AIP", outputFile.getPath()); diff --git a/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java b/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java index 609768bf67..43afbead70 100644 --- a/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java @@ -14,6 +14,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; @@ -33,12 +35,15 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; +import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; /** @@ -46,6 +51,7 @@ import org.springframework.test.util.ReflectionTestUtils; * * @author pvillega */ +@RunWith(MockitoJUnitRunner.class) public class WorkspaceItemTest extends AbstractUnitTest { /** @@ -98,6 +104,7 @@ public class WorkspaceItemTest extends AbstractUnitTest { // "Wire" our spy to be used by the current loaded object services // (To ensure these services use the spy instead of the real service) ReflectionTestUtils.setField(workspaceItemService, "authorizeService", authorizeServiceSpy); + ReflectionTestUtils.setField(itemService, "authorizeService", authorizeServiceSpy); ReflectionTestUtils.setField(collectionService, "authorizeService", authorizeServiceSpy); ReflectionTestUtils.setField(communityService, "authorizeService", authorizeServiceSpy); } catch (AuthorizeException ex) { @@ -158,7 +165,9 @@ public class WorkspaceItemTest extends AbstractUnitTest { @Test public void testCreateAuth() throws Exception { // Allow Collection ADD perms - doNothing().when(authorizeServiceSpy).authorizeAction(context, collection, Constants.ADD); + //doNothing().when(authorizeServiceSpy).authorizeAction(context, collection, Constants.ADD); + doNothing().when(authorizeServiceSpy).authorizeAction(any(Context.class), + any(Collection.class), eq(Constants.ADD)); boolean template; WorkspaceItem created; From fcf077c685b819d8a7e9ac7abef0e54559791732 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Tue, 7 Sep 2021 16:42:28 -0400 Subject: [PATCH 0262/1254] w2p-80200 Remove unecessary comment --- .../src/test/java/org/dspace/content/WorkspaceItemTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java b/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java index 43afbead70..d018a15f97 100644 --- a/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/WorkspaceItemTest.java @@ -165,7 +165,6 @@ public class WorkspaceItemTest extends AbstractUnitTest { @Test public void testCreateAuth() throws Exception { // Allow Collection ADD perms - //doNothing().when(authorizeServiceSpy).authorizeAction(context, collection, Constants.ADD); doNothing().when(authorizeServiceSpy).authorizeAction(any(Context.class), any(Collection.class), eq(Constants.ADD)); From ad148b529e0964fa8de9229d8f4e2186e608bbba Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Mon, 13 Sep 2021 14:40:38 -0400 Subject: [PATCH 0263/1254] w2p-80200 Added auth comment also run tests again --- .../src/test/java/org/dspace/app/packager/PackagerIT.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 037404ef40..f1bf8635bd 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -42,6 +42,11 @@ import org.dspace.services.factory.DSpaceServicesFactory; import org.jdom.Element; import org.junit.Test; +/** + * Basic integration testing for the Packager restore feature + * + * @author Nathan Buckingham + */ public class PackagerIT extends AbstractIntegrationTestWithDatabase { private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); From 1a5b7475f4ddd90d40a9882a397483041608c0e3 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Tue, 14 Sep 2021 16:21:33 +0200 Subject: [PATCH 0264/1254] fixed NPE on item retrieving --- ...VersionHistoryDraftVersionLinkRepository.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java index 343bb9e9d3..f1cf2d0bc9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java @@ -65,13 +65,15 @@ public class VersionHistoryDraftVersionLinkRepository extends AbstractDSpaceRest } Version oldestVersion = versionHistoryService.getLatestVersion(context, versionHistory); - WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); - WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); - if (Objects.nonNull(workflowItem)) { - return converter.toRest(workflowItem, projection); - } - if (Objects.nonNull(workspaceItem)) { - return converter.toRest(workspaceItem, projection); + if(Objects.nonNull(oldestVersion) && Objects.nonNull(oldestVersion.getItem())) { + WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); + if (Objects.nonNull(workflowItem)) { + return converter.toRest(workflowItem, projection); + } + if (Objects.nonNull(workspaceItem)) { + return converter.toRest(workspaceItem, projection); + } } return null; } From 96db8d2afc1e8236b9708566d006b1bb29dae7aa Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Sep 2021 18:29:14 +0200 Subject: [PATCH 0265/1254] added javaDocs --- .../org/dspace/versioning/VersionHistory.java | 8 ++++++- .../versioning/VersioningServiceImpl.java | 4 +--- .../org/dspace/versioning/dao/VersionDAO.java | 14 +++++++++++ .../versioning/service/VersioningService.java | 24 +++++++++++++++++++ ...sionHistoryDraftVersionLinkRepository.java | 18 +++++++------- 5 files changed, 55 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java index af49dd2e81..ffa58639af 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java @@ -9,6 +9,7 @@ package org.dspace.versioning; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -86,8 +87,13 @@ public class VersionHistory implements ReloadableEntity { this.versions.remove(version); } + /** + * Verify if there is a version's item in submission. + * + * @return true if the last version in submission, otherwise false. + */ public boolean hasDraftVersion() { - if (!CollectionUtils.isNullOrEmpty(versions)) { + if (!CollectionUtils.isNullOrEmpty(versions) && Objects.nonNull(versions.get(0).getItem())) { return !versions.get(0).getItem().isArchived(); } return false; diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java index b5dee180e5..ece536e81b 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningServiceImpl.java @@ -258,9 +258,7 @@ public class VersioningServiceImpl implements VersioningService { @Override public void update(Context context, Version version) throws SQLException { - if (version != null) { - versionDAO.save(context, version); - } + versionDAO.save(context, version); } @Override diff --git a/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java b/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java index 21bec23c19..6730ece3f5 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java +++ b/dspace-api/src/main/java/org/dspace/versioning/dao/VersionDAO.java @@ -36,6 +36,8 @@ public interface VersionDAO extends GenericDAO { * * @param context The relevant DSpace Context. * @param versionHistory version history + * @param offset the position of the first result to return + * @param limit paging limit * @return all versions of an version history that have items assigned. * @throws SQLException An exception that provides information on a database access error or other errors. */ @@ -44,6 +46,18 @@ public interface VersionDAO extends GenericDAO { public int getNextVersionNumber(Context c, VersionHistory vh) throws SQLException; + /** + * This method count versions of an version history that have items + * assigned. We do not delete versions to keep version numbers stable. To + * remove a version we set the item, date, summary and eperson null. This + * method returns only versions that aren't soft deleted and have items + * assigned. + * + * @param context The relevant DSpace Context. + * @param versionHistory Version history + * @return Total versions of an version history that have items assigned. + * @throws SQLException If database error + */ public int countVersionsByHistoryWithItem(Context context, VersionHistory versionHistory) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java b/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java index dcc87c56aa..2f6df5b732 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java +++ b/dspace-api/src/main/java/org/dspace/versioning/service/VersioningService.java @@ -40,6 +40,18 @@ public interface VersioningService { */ List getVersionsByHistory(Context c, VersionHistory vh) throws SQLException; + /** + * Return a paginated list of versions of a version history. + * To keep version numbers stable we do not delete versions, we do only set + * the item, date, summary and eperson null. This methods returns only those + * versions that have an item assigned. + * + * @param c The relevant DSpace Context. + * @param vh Version history + * @param offset The position of the first result to return + * @param limit Paging limit + * @throws SQLException If database error + */ List getVersionsByHistoryWithItems(Context c, VersionHistory vh, int offset, int limit) throws SQLException; @@ -76,6 +88,18 @@ public interface VersioningService { */ public void update(Context context, Version version) throws SQLException; + /** + * This method count versions of an version history that have items + * assigned. We do not delete versions to keep version numbers stable. To + * remove a version we set the item, date, summary and eperson null. This + * method returns only versions that aren't soft deleted and have items + * assigned. + * + * @param context The relevant DSpace Context. + * @param versionHistory Version history + * @return Total versions of an version history that have items assigned. + * @throws SQLException If database error + */ public int countVersionsByHistoryWithItem(Context context, VersionHistory versionHistory) throws SQLException; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java index f1cf2d0bc9..91819872aa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java @@ -65,15 +65,15 @@ public class VersionHistoryDraftVersionLinkRepository extends AbstractDSpaceRest } Version oldestVersion = versionHistoryService.getLatestVersion(context, versionHistory); - if(Objects.nonNull(oldestVersion) && Objects.nonNull(oldestVersion.getItem())) { - WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); - WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); - if (Objects.nonNull(workflowItem)) { - return converter.toRest(workflowItem, projection); - } - if (Objects.nonNull(workspaceItem)) { - return converter.toRest(workspaceItem, projection); - } + if (Objects.nonNull(oldestVersion) && Objects.nonNull(oldestVersion.getItem())) { + WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); + if (Objects.nonNull(workflowItem)) { + return converter.toRest(workflowItem, projection); + } + if (Objects.nonNull(workspaceItem)) { + return converter.toRest(workspaceItem, projection); + } } return null; } From 2fd4625a7e8d0e3516476e1aaf1a09fc397618dd Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Tue, 14 Sep 2021 14:13:38 -0400 Subject: [PATCH 0266/1254] Rebased main --- .../src/main/java/org/dspace/content/ItemServiceImpl.java | 2 +- .../src/test/java/org/dspace/app/packager/PackagerIT.java | 3 +-- 2 files changed, 2 insertions(+), 3 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 2244d4b149..0ce132d77b 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -450,7 +450,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It context.addEvent(new Event(Event.CREATE, Constants.ITEM, item.getID(), null, getIdentifiers(context, item))); - log.info(LogManager.getHeader(context, "create_item", "item_id=" + item.getID())); + log.info(LogHelper.getHeader(context, "create_item", "item_id=" + item.getID())); return item; } diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index f1bf8635bd..262fac3ab9 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -10,7 +10,6 @@ package org.dspace.app.packager; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.File; @@ -163,7 +162,7 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { Iterator items = itemService.findByCollection(context, col1); Item testItem = items.next(); assertFalse(items.hasNext()); //check to make sure there is only 1 item - assertSame("123456789/100", testItem.getHandle()); //check to make sure the item wasn't overwritten as + assertEquals("123456789/100", testItem.getHandle()); //check to make sure the item wasn't overwritten as // it would have the old handle. } finally { tempFile.delete(); From b254c8eeea9c37d040a9c52da59abe6580b245d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 15 Sep 2021 17:23:14 +0100 Subject: [PATCH 0267/1254] change entry id to a base64 value. A workaround solution for issue #7944 --- .../impl/OpenAIREFundingDataProvider.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java index 51bb4706a5..e424e08966 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java @@ -12,6 +12,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -80,13 +81,19 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { @Override public Optional getExternalDataObject(String id) { - Response response = searchByProjectURI(id); + + // we use base64 encoding in order to use slashes / and other + // characters that must be escaped for the <:entry-id> + String decodedId = new String(Base64.getDecoder().decode(id)); + Response response = searchByProjectURI(decodedId); try { if (response.getHeader() != null && Integer.parseInt(response.getHeader().getTotal()) > 0) { Project project = response.getResults().getResult().get(0).getMetadata().getEntity().getProject(); - ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider.ExternalDataObjectBuilder( - project).setId(generateProjectURI(project)).setSource(sourceIdentifier).build(); + ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider.ExternalDataObjectBuilder(project) + .setId(generateProjectURI(project)) + .setSource(sourceIdentifier) + .build(); return Optional.of(externalDataObject); } } catch (NumberFormatException e) { @@ -130,7 +137,9 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { if (projects.size() > 0) { return projects.stream() .map(project -> new OpenAIREFundingDataProvider.ExternalDataObjectBuilder(project) - .setId(generateProjectURI(project)).setSource(sourceIdentifier).build()) + .setId(generateProjectURI(project)) + .setSource(sourceIdentifier) + .build()) .collect(Collectors.toList()); } return Collections.emptyList(); @@ -337,7 +346,11 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { * @return ExternalDataObjectBuilder */ public ExternalDataObjectBuilder setId(String id) { - this.externalDataObject.setId(id); + + // we use base64 encoding in order to use slashes / and other + // characters that must be escaped for the <:entry-id> + String base64Id = Base64.getEncoder().encodeToString(id.getBytes()); + this.externalDataObject.setId(base64Id); return this; } From 99036178f4e6ccf6b84abfb85c5636ea9eba1766 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 15 Sep 2021 14:33:01 -0400 Subject: [PATCH 0268/1254] [DS-3952] Check user input for valid email, escape the rest to avoid XSS. --- dspace-server-webapp/pom.xml | 10 ++++++- .../app/rest/model/RequestItemRest.java | 13 +++++++++ .../repository/RequestItemRepository.java | 29 ++++++++++++++----- .../app/rest/RequestItemRepositoryIT.java | 8 +++++ 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 67926a5562..686bd91edf 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -387,6 +387,15 @@ org.apache.commons commons-collections4 + + org.apache.commons + commons-text + 1.8 + + + commons-validator + commons-validator + joda-time joda-time @@ -514,7 +523,6 @@ lucene-analyzers-stempel test - diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java index 64f1bb23d4..616a2d631d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RequestItemRest.java @@ -30,15 +30,28 @@ public class RequestItemRest public static final String CATEGORY = RestAddressableModel.TOOLS; protected String bitstream_id; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) protected Date decisionDate; + protected Date expires; + protected String item_id; + protected String reqEmail; + protected String reqMessage; + protected String reqName; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) protected Date requestDate; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) protected String token; + protected boolean acceptRequest; + protected boolean allfiles; /** 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 793bf84d3e..bc8e652bd6 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 @@ -16,6 +16,8 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.text.StringEscapeUtils; +import org.apache.commons.validator.routines.EmailValidator; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; @@ -23,7 +25,7 @@ import org.dspace.app.rest.exception.IncompleteItemRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RequestItemRest; -import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Item; @@ -56,14 +58,18 @@ public class RequestItemRepository @Autowired(required = true) protected RequestItemConverter requestItemConverter; - @Override + /* + * DSpaceRestRepository + */ + @PreAuthorize("permitAll()") - public RequestItemRest findOne(Context context, String id) { - RequestItem requestItem = requestItemService.findByToken(context, id); + @Override + public RequestItemRest findOne(Context context, String token) { + RequestItem requestItem = requestItemService.findByToken(context, token); if (null == requestItem) { return null; } else { - return requestItemConverter.convert(requestItem, new DefaultProjection()); + return requestItemConverter.convert(requestItem, Projection.DEFAULT); } } @@ -114,9 +120,16 @@ public class RequestItemRepository if (isBlank(email)) { throw new IncompleteItemRequestException("A submitter's email address is required"); } + EmailValidator emailValidator = EmailValidator.getInstance(false, false); + if (!emailValidator.isValid(email)) { + throw new UnprocessableEntityException("Invalid email address"); + } - String username = rir.getRequestName(); - String message = rir.getRequestMessage(); + // Escape username to evade nasty XSS attempts + String username = StringEscapeUtils.escapeHtml4(rir.getRequestName()); + + // Escape message text to evade nasty XSS attempts + String message = StringEscapeUtils.escapeHtml4(rir.getRequestMessage()); token = requestItemService.createRequest(ctx, bitstream, item, allFiles, email, username, message); @@ -129,7 +142,7 @@ public class RequestItemRepository RequestItem ri = requestItemService.findByToken(ctx, token); ri.setAccept_request(false); // Not accepted yet. Must set: DS-4032 requestItemService.update(ctx, ri); - return requestItemConverter.convert(ri, new DefaultProjection()); + return requestItemConverter.convert(ri, Projection.DEFAULT); } // NOTICE: there is no service method for this -- requests are never deleted? 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 5f2ca02c6c..221d72b977 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 @@ -361,6 +361,14 @@ public class RequestItemRepositoryIT .content(mapper.writeValueAsBytes(rir)) .contentType(contentType)) .andExpect(status().isUnprocessableEntity()); + + // Test bad email + rir.setRequestEmail(""); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); } finally { // Clean up the created request. RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); From 66e6e50cabceb9bcb2c198d8d217e5980e87d2f6 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 15 Sep 2021 15:08:37 -0400 Subject: [PATCH 0269/1254] [DS-3952] Catch up with other development. --- .../org/dspace/app/requestitem/RequestItemServiceImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 ac032718fb..974d9979b0 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 @@ -11,13 +11,14 @@ import java.sql.SQLException; import java.util.Date; import java.util.List; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.dao.RequestItemDAO; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; -import org.dspace.core.LogManager; +import org.dspace.core.LogHelper; import org.dspace.core.Utils; import org.springframework.beans.factory.annotation.Autowired; @@ -31,7 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class RequestItemServiceImpl implements RequestItemService { - private final Logger log = org.apache.logging.log4j.LogManager.getLogger(); + private final Logger log = LogManager.getLogger(); @Autowired(required = true) protected RequestItemDAO requestItemDAO; @@ -88,7 +89,7 @@ public class RequestItemServiceImpl implements RequestItemService { @Override public void delete(Context context, RequestItem requestItem) { - log.debug(LogManager.getHeader(context, "delete_itemrequest", "request_id={}"), + log.debug(LogHelper.getHeader(context, "delete_itemrequest", "request_id={}"), requestItem.getID()); try { requestItemDAO.delete(context, requestItem); From 372b9e2ce6e0f78307232909d487ba81fbce0ccb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 15 Sep 2021 14:33:25 -0500 Subject: [PATCH 0270/1254] Add 'dspace database migrate force' to manually trigger Flyway Callbacks --- .../dspace/storage/rdbms/DatabaseUtils.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) 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 98777c654b..dc43519bbb 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 @@ -180,8 +180,9 @@ public class DatabaseUtils { try (Connection connection = dataSource.getConnection()) { System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - // "migrate" allows for an OPTIONAL second argument: + // "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")) { @@ -191,6 +192,8 @@ public class DatabaseUtils { // 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! @@ -654,6 +657,34 @@ public class DatabaseUtils { protected static synchronized void updateDatabase(DataSource datasource, Connection connection, String targetVersion, boolean outOfOrder) throws SQLException { + updateDatabase(datasource, connection, targetVersion, outOfOrder, false); + } + + /** + * Ensures the current database is up-to-date with regards + * to the latest DSpace DB schema. If the scheme is not up-to-date, + * then any necessary database migrations are performed. + *

    + * FlywayDB (http://flywaydb.org/) is used to perform database migrations. + * If a Flyway DB migration fails it will be rolled back to the last + * successful migration, and any errors will be logged. + * + * @param datasource DataSource object (retrieved from DatabaseManager()) + * @param connection Database connection + * @param targetVersion If specified, only migrate the database to a particular *version* of DSpace. This is + * just useful for testing migrations, and should NOT be used in Production. + * If null, the database is migrated to the latest version. + * @param outOfOrder If true, Flyway will run any lower version migrations that were previously "ignored". + * If false, Flyway will only run new migrations with a higher version number. + * @param forceMigrate If true, always run a Flyway migration, even if no "Pending" migrations exist. This can be + * used to trigger Flyway Callbacks manually. + * If false, only run migration if pending migrations exist, otherwise do nothing. + * @throws SQLException if database error + * If database cannot be upgraded. + */ + protected static synchronized void updateDatabase(DataSource datasource, Connection connection, + String targetVersion, boolean outOfOrder, boolean forceMigrate) + throws SQLException { if (null == datasource) { throw new SQLException("The datasource is a null reference -- cannot continue."); } @@ -730,6 +761,10 @@ public class DatabaseUtils { // Flag that Discovery will need reindexing, since database was updated setReindexDiscovery(reindexAfterUpdate); + } else if (forceMigrate) { + log.info("DSpace database schema is up to date, but 'force' was specified. " + + "Running migrate command to trigger callbacks."); + flyway.migrate(); } else { log.info("DSpace database schema is up to date"); } From 253980095564c54a0a40ec7aacfe30cfc2c7d073 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 15 Sep 2021 16:29:30 -0700 Subject: [PATCH 0271/1254] Made the search service a configurable plugin. --- .../iiif/service/SearchAnnotationService.java | 4 +- .../app/rest/iiif/service/SearchService.java | 22 +++++-- .../iiif/service/WordHighlightSolrSearch.java | 62 +++++++++++-------- dspace/config/dspace.cfg | 4 +- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java index da113555cb..7bf1fab919 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java @@ -11,8 +11,10 @@ import java.util.UUID; public interface SearchAnnotationService { - void initializeQuery(String endpoint, String manifestId, boolean validationEnabled); + void initializeQuerySettings(String endpoint, String manifestId); String getSolrSearchResponse(UUID uuid, String query); + boolean getSearchPlugin(String className); + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index fa3d834bb4..dae4364ecf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.iiif.service; +import java.util.List; import java.util.UUID; import org.apache.logging.log4j.Logger; @@ -23,14 +24,16 @@ import org.springframework.web.context.annotation.RequestScope; public class SearchService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SearchService.class); - private final boolean validationEnabled; + + private final String searchPlugin; @Autowired - WordHighlightSolrSearch annotationService; + List annotationService; public SearchService(ConfigurationService configurationService) { - validationEnabled = configurationService - .getBooleanProperty("discovery.solr.url.validation.enabled", true); + setConfiguration(configurationService); + // The search service to use is defined in dspace configuration. + searchPlugin = configurationService.getProperty("iiif.search.plugin"); } /** @@ -41,8 +44,15 @@ public class SearchService extends AbstractResourceService { * @return IIIF search result with page coordinate annotations. */ public String searchWithinManifest(UUID uuid, String query) { - annotationService.initializeQuery(IIIF_ENDPOINT, getManifestId(uuid), validationEnabled); - return annotationService.getSolrSearchResponse(uuid, query); + for (SearchAnnotationService service : annotationService) { + if (service.getSearchPlugin(searchPlugin)) { + service.initializeQuerySettings(IIIF_ENDPOINT, getManifestId(uuid)); + return service.getSolrSearchResponse(uuid, query); + } + } + throw new RuntimeException( + "IIIF search plugin was not found." + ); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java index 0c4f617ff8..e7947453e1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java @@ -36,9 +36,9 @@ import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.discovery.SolrSearchCore; +import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @@ -51,7 +51,6 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(WordHighlightSolrSearch.class); - private final ApplicationContext applicationContext; private String endpoint; private String manifestId; private boolean validationEnabled; @@ -68,14 +67,22 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { @Autowired SolrSearchCore solrSearchCore; + @Autowired + ManifestGenerator manifestGenerator; - public WordHighlightSolrSearch(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } @Override - public void initializeQuery(String endpoint, String manifestId, boolean validationEnabled) { - this.validationEnabled = validationEnabled; + public boolean getSearchPlugin(String className) { + return className.contentEquals(WordHighlightSolrSearch.class.getSimpleName()); + } + + /** + * The query requires these values before it can execute + * @param endpoint the search service endpoint + * @param manifestId the idea of the manifest to search within + */ + @Override + public void initializeQuerySettings(String endpoint, String manifestId) { this.endpoint = endpoint; this.manifestId = manifestId; } @@ -89,10 +96,12 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { @Override public String getSolrSearchResponse(UUID uuid, String query) { String json = ""; - String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("iiif.solr.search.url"); + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + String solrService = configurationService.getProperty("iiif.search.url"); + boolean validationEnabled = configurationService + .getBooleanProperty("discovery.solr.url.validation.enabled"); UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); - if (urlValidator.isValid(solrService) || this.validationEnabled) { + if (urlValidator.isValid(solrService) || validationEnabled) { HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); solrServer.setUseMultiPartPost(true); SolrQuery solrQuery = getSolrQuery(adjustQuery(query), manifestId); @@ -157,9 +166,8 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { * The identifier values must be aligned with zero-based IIIF canvas identifiers: * c0, c1, c2.... * - * The convention convention for Alto IDs must be followed when indexing ALTO files - * into the word_highlighting solr index. If it is not, search responses will not - * match canvases. + * This convention must be followed when indexing ALTO files into the word_highlighting + * solr index. If it is not followed, word highlights will not align canvases. * * @param json solr search result * @param query the solr query @@ -217,7 +225,11 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { } /** - * Returns position of canvas by extracting from the pages id element. + * Returns position of canvas. Uses the "pages" id attribute. + * This method assumes that the solr response includes a "page" id attribute that is + * delimited with a "." and that the integer corresponds to the + * canvas identifier in the manifest. For METS/ALTO documents, the page + * order can be derived from the METS file when loading the solr index. * @param element the pages element * @return canvas id */ @@ -240,24 +252,24 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { */ private AnnotationGenerator createSearchResultAnnotation(String params, String text, String pageId, UUID uuid) { String identifier = this.endpoint + uuid + "/annot/" + pageId + "-" + params; - AnnotationGenerator annotation = applicationContext - .getBean(AnnotationGenerator.class, identifier, AnnotationGenerator.PAINTING); - CanvasGenerator canvas = - applicationContext.getBean(CanvasGenerator.class, identifier); - annotation.setOnCanvas(canvas); contentAsText.setText(text); - annotation.setResource(contentAsText); - annotation.setWithin(getWithinManifest()); - return annotation; + CanvasGenerator canvas = new CanvasGenerator().setIdentifier(identifier); + + AnnotationGenerator annotationGenerator = new AnnotationGenerator() + .setMotivation(AnnotationGenerator.PAINTING) + .setIdentifier(identifier) + .setOnCanvas(canvas) + .setResource(contentAsText) + .setWithin(getWithinManifest()); + + return annotationGenerator; } private List getWithinManifest() { List withinList = new ArrayList<>(); - ManifestGenerator manifestGenerator = applicationContext.getBean(ManifestGenerator.class, manifestId); - manifestGenerator.setLabel("Search within manifest."); + manifestGenerator.setIdentifier(manifestId); withinList.add(manifestGenerator); return withinList; - } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 7378bfcbb4..30a939a667 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1519,7 +1519,9 @@ iiif.url = ${dspace.server.url}/iiif/ iiif.image.server = http://localhost:8182/iiif/2/ # Base URL of the solr search index (IIIF Search API). -iiif.solr.search.url = http://localhost:8983/solr/word_highlighting +iiif.search.url = http://localhost:8983/solr/word_highlighting + +iiif.search.plugin = WordHighlightSolrSearch iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams From 0affa792168352d5fdf4aabe1cecc6077991b31f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 15 Sep 2021 16:30:42 -0700 Subject: [PATCH 0272/1254] Removed remaining use of generator bean factories. --- .../model/generator/AnnotationGenerator.java | 92 +++++++++++++++---- .../generator/AnnotationListGenerator.java | 2 +- .../generator/ExternalLinksGenerator.java | 5 +- .../iiif/service/AbstractResourceService.java | 2 +- .../iiif/service/AnnotationListService.java | 15 ++- .../rest/iiif/service/ManifestService.java | 2 +- .../app/rest/iiif/service/SeeAlsoService.java | 15 ++- 7 files changed, 94 insertions(+), 39 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java index 508dc6e58c..ea7cc712fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java @@ -13,15 +13,12 @@ import java.util.List; import de.digitalcollections.iiif.model.Motivation; import de.digitalcollections.iiif.model.openannotation.Annotation; import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; + /** * Annotations associate content resources and commentary with a canvas. * This is used for the otherContent AnnotationList and Search response. */ -@Component -@Scope("prototype") public class AnnotationGenerator implements IIIFResource { public static final String TYPE = "sc:AnnotationList"; @@ -29,35 +26,94 @@ public class AnnotationGenerator implements IIIFResource { public static final Motivation COMMENTING = new Motivation("oa:commenting"); public static final Motivation LINKING = new Motivation("oa:linking"); - private Annotation annotation; + private Motivation motivation; + private String identifier; + private CanvasGenerator canvasGenerator; + private ContentAsTextGenerator contentAsTextGenerator; + private ExternalLinksGenerator externalLinksGenerator; + List manifests = new ArrayList<>(); - public AnnotationGenerator(String identifier, Motivation motivation) { - annotation = new Annotation(identifier, motivation); + + /** + * Set the annotation identifier. Required. + * @param identifier + * @return + */ + public AnnotationGenerator setIdentifier(String identifier) { + this.identifier = identifier; + return this; } - public void setOnCanvas(CanvasGenerator canvas) { - annotation.setOn(canvas.getResource()); + /** + * Sets the annotation motivtion. Required. + * @param motivation + * @return + */ + public AnnotationGenerator setMotivation(Motivation motivation) { + this.motivation = motivation; + return this; } - public void setResource(org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator contentAsText) { - annotation.setResource(contentAsText.getResource()); + /** + * Set the canvas for this annotation. + * @param canvas + * @return + */ + public AnnotationGenerator setOnCanvas(CanvasGenerator canvas) { + this.canvasGenerator = canvas; + return this; } - public void setResource(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { - annotation.setResource(otherContent.getResource()); + /** + * Set a text resource for this annotation. + * @param contentAsText + * @return + */ + public AnnotationGenerator setResource(ContentAsTextGenerator contentAsText) { + this.contentAsTextGenerator = contentAsText; + return this; } - public void setWithin(List within) { - List manifests = new ArrayList<>(); + /** + * Set the external link for this annotation. + * @param otherContent + * @return + */ + public AnnotationGenerator setResource(ExternalLinksGenerator otherContent) { + this.externalLinksGenerator = otherContent; + return this; + } + + /** + * Set the within property for this annotation. This property + * is a list of manifests. The property is renamed to partOf in v3 + * @param within + * @return + */ + public AnnotationGenerator setWithin(List within) { for (ManifestGenerator manifest : within) { - manifests.add(manifest.getResource()); + this.manifests.add(manifest.getResource()); } - // property renamed to partOf in v3 - annotation.setWithin(manifests); + return this; } @Override public Resource getResource() { + if (identifier == null || motivation == null) { + throw new RuntimeException("Annotations require both an identifier and a motivation"); + } + Annotation annotation = new Annotation(identifier, motivation); + annotation.setWithin(manifests); + // These optional annotation fields vary with the context. + if (canvasGenerator != null) { + annotation.setOn(canvasGenerator.getResource()); + } + if (externalLinksGenerator != null) { + annotation.setResource(externalLinksGenerator.getResource()); + } + if (contentAsTextGenerator != null) { + annotation.setResource(contentAsTextGenerator.getResource()); + } return annotation; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java index 6b51e3775b..be6faa970c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java @@ -37,7 +37,7 @@ public class AnnotationListGenerator implements org.dspace.app.rest.iiif.model.g * Adds Annotation resource to the annotation list. * @param annotation the Annotation Resource */ - public void addResource(org.dspace.app.rest.iiif.model.generator.AnnotationGenerator annotation) { + public void addResource(AnnotationGenerator annotation) { this.annotations.add((Annotation) annotation.getResource()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java index 8d77447758..d5bb479e0e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java @@ -14,7 +14,7 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** - * Facade for the IIIF Presentation API version 2.1.1 "OtherContent" domain model class. + * Facade for the API version 2.1.1 "OtherContent" domain model. * * This is the type for Content resources such as images or texts that are associated with a canvas. * Used in the "related", "renderings" and "otherContent" fields of IIIF resources. @@ -69,6 +69,9 @@ public class ExternalLinksGenerator implements IIIFResource { @Override public Resource getResource() { + if (identifier == null) { + throw new RuntimeException("Annotation requires an identifier"); + } OtherContent otherContent; if (format != null) { otherContent = new OtherContent(identifier, format); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 59e9a20be3..17a8bf3b96 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -60,7 +60,7 @@ public abstract class AbstractResourceService { protected void setConfiguration(ConfigurationService configurationService) { IIIF_ENDPOINT = configurationService.getProperty("iiif.url"); IMAGE_SERVICE = configurationService.getProperty("iiif.image.server"); - SEARCH_URL = configurationService.getProperty("iiif.solr.search.url"); + SEARCH_URL = configurationService.getProperty("iiif.search.url"); BITSTREAM_PATH_PREFIX = configurationService.getProperty("iiif.bitstream.url"); DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint"); CLIENT_URL = configurationService.getProperty("dspace.ui.url"); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java index 4b35249dca..9a115f8bf2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java @@ -25,7 +25,6 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @@ -33,7 +32,6 @@ import org.springframework.web.context.annotation.RequestScope; @RequestScope public class AnnotationListService extends AbstractResourceService { - ApplicationContext applicationContext; @Autowired IIIFUtils utils; @@ -54,9 +52,8 @@ public class AnnotationListService extends AbstractResourceService { AnnotationListGenerator annotationList; - public AnnotationListService(ApplicationContext applicationContext, ConfigurationService configurationService) { + public AnnotationListService(ConfigurationService configurationService) { setConfiguration(configurationService); - this.applicationContext = applicationContext; } /** @@ -99,11 +96,11 @@ public class AnnotationListService extends AbstractResourceService { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - AnnotationGenerator annotation = applicationContext - .getBean(AnnotationGenerator.class, IIIF_ENDPOINT + bitstream.getID() - + "/annot", AnnotationGenerator.LINKING); - annotation.setResource(getLinksGenerator(mimetype, bitstream)); - annotationList.addResource(annotation); + AnnotationGenerator annotationGenerator = new AnnotationGenerator() + .setIdentifier(IIIF_ENDPOINT + bitstream.getID() + "/annot") + .setMotivation(AnnotationGenerator.LINKING) + .setResource(getLinksGenerator(mimetype, bitstream)); + annotationList.addResource(annotationGenerator); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 5bd6b9ab8c..cc845b7cf8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -193,7 +193,7 @@ public class ManifestService extends AbstractResourceService { private void addSeeAlso(Item item) { List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); if (bundles.size() > 0) { - manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item, bundles)); + manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item.getID())); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java index 79a8bea0d0..e6cdd1dc88 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java @@ -7,32 +7,31 @@ */ package org.dspace.app.rest.iiif.service; -import java.util.List; +import java.util.UUID; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; -import org.dspace.content.Bundle; -import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; + @Component @RequestScope public class SeeAlsoService extends AbstractResourceService { + @Autowired + ExternalLinksGenerator externalLinksGenerator; + private static final String SEE_ALSO_LABEL = "More descriptions of this resource"; public SeeAlsoService(ConfigurationService configurationService) { setConfiguration(configurationService); } - @Autowired - ExternalLinksGenerator externalLinksGenerator; - - public ExternalLinksGenerator getSeeAlso(Item item, List bundles) { - return externalLinksGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso") + public ExternalLinksGenerator getSeeAlso(UUID itemId) { + return externalLinksGenerator.setIdentifier(IIIF_ENDPOINT + itemId + "/manifest/seeAlso") .setType(AnnotationGenerator.TYPE) .setLabel(SEE_ALSO_LABEL); From 39bd3aceb5bd8e4b003b19672233c89a7b17286f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 16 Sep 2021 12:15:19 +0200 Subject: [PATCH 0273/1254] added test to verify the correct functioning of versioning items with entity type --- .../app/rest/VersionRestRepositoryIT.java | 124 ++++++++ .../CanCreateVersionFeatureIT.java | 150 ++++++++- .../CanDeleteVersionFeatureIT.java | 105 +++++++ .../CanEditVersionFeatureIT.java | 149 +++++++++ .../CanManageVersionsFeatureIT.java | 288 ++++++++++++++++-- 5 files changed, 789 insertions(+), 27 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 81661860c0..be84e2ace9 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 @@ -630,6 +630,130 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isForbidden()); } + @Test + public void createFirstVersionItemWithentityTypeByAdminAndPropertyBlockEntityEnableTest() throws Exception { + configurationService.setProperty("versioning.block.entity", true); + context.turnOffAuthorisationSystem(); + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .build(); + + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withEntityType("Publication") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + itemA.getID())) + .andExpect(status().isForbidden()); + + configurationService.setProperty("versioning.block.entity", ""); + } + + @Test + public void createFirstVersionItemWithEntityTypeAndPropertyBlockEntityDisabledTest() throws Exception { + configurationService.setProperty("versioning.block.entity", false); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withEntityType("Publication") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + String adminToken = getAuthToken(admin.getEmail(), password); + + try { + getClient(adminToken).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("$.submitterName", is("first (admin) last (admin)")), + hasJsonPath("$.type", is("version")) + ))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + VersionBuilder.delete(idRef.get()); + } + + configurationService.setProperty("versioning.block.entity", ""); + } + + @Test + public void createFirstVersionItemWithEntityTypeBySubmitterAndPropertyBlockEntityDisabledTest() throws Exception { + configurationService.setProperty("versioning.submitterCanCreateNewVersion", true); + configurationService.setProperty("versioning.block.entity", false); + context.turnOffAuthorisationSystem(); + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withEntityType("Publication") + .withSubject("ExtraEntry") + .build(); + + itemA.setSubmitter(eperson); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + String epersonToken = getAuthToken(eperson.getEmail(), password); + try { + getClient(epersonToken).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + itemA.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"))); + } finally { + VersionBuilder.delete(idRef.get()); + } + configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); + configurationService.setProperty("versioning.block.entity", ""); + } + @Test public void patchReplaceSummaryTest() throws Exception { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java index 1f6c84c4c7..2ee953d332 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java @@ -219,7 +219,7 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest } @Test - public void checkCanCreateVersionsFeature999Test() throws Exception { + public void checkCanCreateVersionsFeatureAdminsTest() throws Exception { context.turnOffAuthorisationSystem(); EPerson adminComA = EPersonBuilder.createEPerson(context) .withEmail("testComAdminA@test.com") @@ -307,4 +307,152 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest .andExpect(status().isNotFound()); } + @Test + public void checkCanCreateVersionFeatureAndPropertyBlockEntityEnableTest() throws Exception { + context.turnOffAuthorisationSystem(); + + configurationService.setProperty("versioning.block.entity", true); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withAdminGroup(eperson) + .build(); + + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withEntityType("Publication") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + ItemRest itemRestA = itemConverter.convert(itemA, DefaultProjection.DEFAULT); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenUser = getAuthToken(user.getEmail(), password); + + // define authorization that we know not exists + Authorization user2ItemA = new Authorization(user, canCreateVersionFeature, itemRestA); + Authorization admin2ItemA = new Authorization(admin, canCreateVersionFeature, itemRestA); + Authorization eperson2ItemA = new Authorization(eperson, canCreateVersionFeature, itemRestA); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + admin2ItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenUser).perform(get("/api/authz/authorizations/" + user2ItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2ItemA.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); + } + + @Test + public void checkCanCreateVersionFeatureAndPropertyBlockEntityDisabledTest() throws Exception { + context.turnOffAuthorisationSystem(); + + configurationService.setProperty("versioning.block.entity", false); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .build(); + + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withEntityType("Publication") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + ItemRest itemRestA = itemConverter.convert(itemA, DefaultProjection.DEFAULT); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenUser = getAuthToken(user.getEmail(), password); + + // define authorization that we know not exists + Authorization user2ItemA = new Authorization(user, canCreateVersionFeature, itemRestA); + Authorization admin2ItemA = new Authorization(admin, canCreateVersionFeature, itemRestA); + Authorization eperson2ItemA = new Authorization(eperson, canCreateVersionFeature, itemRestA); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + admin2ItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(admin2ItemA)))); + + getClient(tokenUser).perform(get("/api/authz/authorizations/" + user2ItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2ItemA.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); + } + + @Test + public void checkCanCreateVersionFeatureAndPropertyBlockEntityUnsetedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + configurationService.setProperty("versioning.submitterCanCreateNewVersion", true); + configurationService.setProperty("versioning.block.entity", ""); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withAdminGroup(eperson) + .withSubmitterGroup(eperson) + .build(); + + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withEntityType("Publication") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + ItemRest itemRestA = itemConverter.convert(itemA, DefaultProjection.DEFAULT); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenUser = getAuthToken(user.getEmail(), password); + + // define authorization that we know not exists + Authorization user2ItemA = new Authorization(user, canCreateVersionFeature, itemRestA); + Authorization admin2ItemA = new Authorization(admin, canCreateVersionFeature, itemRestA); + Authorization eperson2ItemA = new Authorization(eperson, canCreateVersionFeature, itemRestA); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + admin2ItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenUser).perform(get("/api/authz/authorizations/" + user2ItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2ItemA.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); + configurationService.setProperty("versioning.submitterCanCreateNewVersion", true); + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java index c4e887abf2..08cdc51108 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java @@ -28,6 +28,7 @@ import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.WorkspaceItemService; import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.hamcrest.Matchers; import org.junit.Before; @@ -46,6 +47,8 @@ public class CanDeleteVersionFeatureIT extends AbstractControllerIntegrationTest @Autowired private WorkspaceItemService workspaceItemService; @Autowired + private ConfigurationService configurationService; + @Autowired private AuthorizationFeatureService authorizationFeatureService; @Autowired private org.dspace.content.service.InstallItemService installItemService; @@ -255,4 +258,106 @@ public class CanDeleteVersionFeatureIT extends AbstractControllerIntegrationTest .andExpect(status().isNotFound()); } + @Test + public void canDeleteVersionFeatureAndPropertyBlockEntityEnableTest() throws Exception { + context.turnOffAuthorisationSystem(); + + configurationService.setProperty("versioning.block.entity", true); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .build(); + + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withEntityType("Publication") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, itemA, "My test summary").build(); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, version.getItem()); + installItemService.installItem(context, workspaceItem); + + context.restoreAuthSystemState(); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + // define authorization that we know not exists + Authorization admin2ItemA = new Authorization(admin, canDeleteVersionFeature, versionRest); + Authorization eperson2ItemA = new Authorization(eperson, canDeleteVersionFeature, versionRest); + Authorization anonymous2ItemA = new Authorization(null, canDeleteVersionFeature, versionRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + admin2ItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2ItemA.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymous2ItemA.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); + } + + @Test + public void canDeleteVersionFeatureAndPropertyBlockEntityDisabledTest() throws Exception { + context.turnOffAuthorisationSystem(); + + configurationService.setProperty("versioning.block.entity", false); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .build(); + + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withEntityType("Publication") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, itemA, "My test summary").build(); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, version.getItem()); + installItemService.installItem(context, workspaceItem); + + context.restoreAuthSystemState(); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + // define authorization that we know not exists + Authorization admin2ItemA = new Authorization(admin, canDeleteVersionFeature, versionRest); + Authorization eperson2ItemA = new Authorization(eperson, canDeleteVersionFeature, versionRest); + Authorization anonymous2ItemA = new Authorization(null, canDeleteVersionFeature, versionRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + admin2ItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(admin2ItemA)))); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2ItemA.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymous2ItemA.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java index e9510b9332..2aa2356ce5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java @@ -25,6 +25,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.hamcrest.Matchers; import org.junit.Before; @@ -41,6 +42,8 @@ public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { @Autowired private VersionConverter versionConverter; @Autowired + private ConfigurationService configurationService; + @Autowired private AuthorizationFeatureService authorizationFeatureService; @@ -198,4 +201,150 @@ public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { .andExpect(status().isNotFound()); } + @Test + public void canEditVersionsFeatureByColAndComAdminsAndPropertyBlockEntityEnableTest() throws Exception { + configurationService.setProperty("versioning.block.entity", true); + context.turnOffAuthorisationSystem(); + EPerson adminComA = EPersonBuilder.createEPerson(context) + .withEmail("testComAdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withEmail("testCol1Admin@test.com") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community A") + .withAdminGroup(adminComA) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withAdminGroup(adminCol1) + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withEntityType("Publication") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "My test summary").build(); + + context.restoreAuthSystemState(); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); + String tokenAdminCol1 = getAuthToken(adminCol1.getEmail(), password); + + // define authorization that we know not exists + Authorization adminOfComAToVersion = new Authorization(adminComA, canEditVersionFeature, versionRest); + Authorization adminOfCol1ToVersion = new Authorization(adminCol1, canEditVersionFeature, versionRest); + Authorization adminToVersion = new Authorization(admin, canEditVersionFeature, versionRest); + + getClient(tokenAdminComA).perform(get("/api/authz/authorizations/" + adminOfComAToVersion.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminCol1).perform(get("/api/authz/authorizations/" + adminOfCol1ToVersion.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToVersion.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); + } + + @Test + public void canEditVersionsFeatureByColAndComAdminsAndPropertyBlockEntityDisabledTest() throws Exception { + configurationService.setProperty("versioning.block.entity", false); + context.turnOffAuthorisationSystem(); + EPerson adminComA = EPersonBuilder.createEPerson(context) + .withEmail("testComAdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withEmail("testCol1Admin@test.com") + .withPassword(password) + .build(); + + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community A") + .withAdminGroup(adminComA) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withAdminGroup(adminCol1) + .build(); + + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withEntityType("Publication") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "My test summary").build(); + + context.restoreAuthSystemState(); + + VersionRest versionRest = versionConverter.convert(version, DefaultProjection.DEFAULT); + + String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); + String tokenAdminCol1 = getAuthToken(adminCol1.getEmail(), password); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPErson = getAuthToken(eperson.getEmail(), password); + + // define authorizations that we know must exists + Authorization adminOfComAToVersion = new Authorization(adminComA, canEditVersionFeature, versionRest); + Authorization adminOfCol1ToVersion = new Authorization(adminCol1, canEditVersionFeature, versionRest); + Authorization adminToVersion = new Authorization(admin, canEditVersionFeature, versionRest); + + // define authorization that we know not exists + Authorization epersonToVersion = new Authorization(eperson, canEditVersionFeature, versionRest); + Authorization anonymousToVersion = new Authorization(null, canEditVersionFeature, versionRest); + + getClient(tokenAdminComA).perform(get("/api/authz/authorizations/" + adminOfComAToVersion.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfComAToVersion)))); + + getClient(tokenAdminCol1).perform(get("/api/authz/authorizations/" + adminOfCol1ToVersion.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfCol1ToVersion)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToVersion.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToVersion)))); + + getClient(tokenEPErson).perform(get("/api/authz/authorizations/" + epersonToVersion.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymousToVersion.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java index 1fd51a9a75..fe4c7cfa45 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java @@ -12,8 +12,11 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.dspace.app.rest.authorization.impl.CanManageVersionsFeature; import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; @@ -26,6 +29,7 @@ import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -46,11 +50,14 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes @Autowired private ConfigurationService configurationService; + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + private Item itemA; - private EPerson user; private ItemRest itemARest; private Community communityA; private Collection collectionA; + private AuthorizationFeature canManageVersionsFeature; final String feature = "canManageVersions"; @@ -60,9 +67,7 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes super.setUp(); context.turnOffAuthorisationSystem(); - user = EPersonBuilder.createEPerson(context) - .withEmail("userEmail@test.com") - .withPassword(password).build(); + canManageVersionsFeature = authorizationFeatureService.find(CanManageVersionsFeature.NAME); communityA = CommunityBuilder.createCommunity(context) .withName("communityA").build(); @@ -114,41 +119,272 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes } @Test - public void submitterItemSuccessTest() throws Exception { + public void canManageVersionsFeatureAdminsTest() throws Exception { context.turnOffAuthorisationSystem(); + EPerson adminComA = EPersonBuilder.createEPerson(context) + .withEmail("testComAdminA@test.com") + .withPassword(password) + .build(); - configurationService.setProperty("versioning.submitterCanCreateNewVersion", true); - itemA.setSubmitter(user); + EPerson adminComB = EPersonBuilder.createEPerson(context) + .withEmail("testComBdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withEmail("testCol1Admin@test.com") + .withPassword(password) + .build(); + + EPerson adminCol2 = EPersonBuilder.createEPerson(context) + .withEmail("testCol2Admin@test.com") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community A") + .withAdminGroup(adminComA) + .build(); + + CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community B") + .withAdminGroup(adminComB) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withAdminGroup(adminCol1) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 2") + .withAdminGroup(adminCol2) + .build(); + + Item itemA = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); context.restoreAuthSystemState(); - String userToken = getAuthToken(user.getEmail(), password); - getClient(userToken).perform(get("/api/authz/authorizations/search/object") - .param("embed", "feature") - .param("feature", feature) - .param("uri", utils.linkToSingleResource(itemARest, "self").getHref())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) - .andExpect(jsonPath("$._embedded").exists()); + ItemRest itemRestA = itemConverter.convert(itemA, DefaultProjection.DEFAULT); + + String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); + String tokenAdminComB = getAuthToken(adminComB.getEmail(), password); + String tokenAdminCol1 = getAuthToken(adminCol1.getEmail(), password); + String tokenAdminCol2 = getAuthToken(adminCol2.getEmail(), password); + + // define authorizations that we know must exists + Authorization adminOfComAToItemA = new Authorization(adminComA, canManageVersionsFeature, itemRestA); + Authorization adminOfCol1ToItemA = new Authorization(adminCol1, canManageVersionsFeature, itemRestA); + + // define authorization that we know not exists + Authorization adminOfComBToItemA = new Authorization(adminComB, canManageVersionsFeature, itemRestA); + Authorization adminOfCol2ToItemA = new Authorization(adminCol2, canManageVersionsFeature, itemRestA); + + getClient(tokenAdminComA).perform(get("/api/authz/authorizations/" + adminOfComAToItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfComAToItemA)))); + + getClient(tokenAdminCol1).perform(get("/api/authz/authorizations/" + adminOfCol1ToItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfCol1ToItemA)))); + + getClient(tokenAdminComB).perform(get("/api/authz/authorizations/" + adminOfComBToItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminCol2).perform(get("/api/authz/authorizations/" + adminOfCol2ToItemA.getID())) + .andExpect(status().isNotFound()); } @Test - public void submitterItemWithPropertySubmitterCanCreateNewVersionIsFalseTest() throws Exception { + public void canManageVersionsFeatureAdminsAndPropertyBlockEntityEnableTest() throws Exception { + configurationService.setProperty("versioning.block.entity", true); context.turnOffAuthorisationSystem(); + EPerson adminComA = EPersonBuilder.createEPerson(context) + .withEmail("testComAdminA@test.com") + .withPassword(password) + .build(); - configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); - itemA.setSubmitter(user); + EPerson adminComB = EPersonBuilder.createEPerson(context) + .withEmail("testComBdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withEmail("testCol1Admin@test.com") + .withPassword(password) + .build(); + + EPerson adminCol2 = EPersonBuilder.createEPerson(context) + .withEmail("testCol2Admin@test.com") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community A") + .withAdminGroup(adminComA) + .build(); + + CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community B") + .withAdminGroup(adminComB) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withAdminGroup(adminCol1) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 2") + .withAdminGroup(adminCol2) + .build(); + + Item itemA = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withEntityType("Publication") + .withSubject("ExtraEntry") + .build(); context.restoreAuthSystemState(); - String userToken = getAuthToken(user.getEmail(), password); - getClient(userToken).perform(get("/api/authz/authorizations/search/object") - .param("embed", "feature") - .param("feature", feature) - .param("uri", utils.linkToSingleResource(itemARest, "self").getHref())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$._embedded").doesNotExist()); + ItemRest itemRestA = itemConverter.convert(itemA, DefaultProjection.DEFAULT); + + String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); + String tokenAdminComB = getAuthToken(adminComB.getEmail(), password); + String tokenAdminCol1 = getAuthToken(adminCol1.getEmail(), password); + String tokenAdminCol2 = getAuthToken(adminCol2.getEmail(), password); + + // define authorization that we know not exists + Authorization adminOfComAToItemA = new Authorization(adminComA, canManageVersionsFeature, itemRestA); + Authorization adminOfCol1ToItemA = new Authorization(adminCol1, canManageVersionsFeature, itemRestA); + Authorization adminOfComBToItemA = new Authorization(adminComB, canManageVersionsFeature, itemRestA); + Authorization adminOfCol2ToItemA = new Authorization(adminCol2, canManageVersionsFeature, itemRestA); + + getClient(tokenAdminComA).perform(get("/api/authz/authorizations/" + adminOfComAToItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminCol1).perform(get("/api/authz/authorizations/" + adminOfCol1ToItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminComB).perform(get("/api/authz/authorizations/" + adminOfComBToItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminCol2).perform(get("/api/authz/authorizations/" + adminOfCol2ToItemA.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); + } + + @Test + public void canManageVersionsFeatureAdminsAndPropertyBlockEntityDisabledTest() throws Exception { + configurationService.setProperty("versioning.block.entity", false); + context.turnOffAuthorisationSystem(); + EPerson adminComA = EPersonBuilder.createEPerson(context) + .withEmail("testComAdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminComB = EPersonBuilder.createEPerson(context) + .withEmail("testComBdminA@test.com") + .withPassword(password) + .build(); + + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withEmail("testCol1Admin@test.com") + .withPassword(password) + .build(); + + EPerson adminCol2 = EPersonBuilder.createEPerson(context) + .withEmail("testCol2Admin@test.com") + .withPassword(password) + .build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community A") + .withAdminGroup(adminComA) + .build(); + + CommunityBuilder.createSubCommunity(context, rootCommunity) + .withName("Sub Community B") + .withAdminGroup(adminComB) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withAdminGroup(adminCol1) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withName("Collection 2") + .withAdminGroup(adminCol2) + .build(); + + Item itemA = ItemBuilder.createItem(context, col1) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withEntityType("Publication") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + ItemRest itemRestA = itemConverter.convert(itemA, DefaultProjection.DEFAULT); + + String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); + String tokenAdminComB = getAuthToken(adminComB.getEmail(), password); + String tokenAdminCol1 = getAuthToken(adminCol1.getEmail(), password); + String tokenAdminCol2 = getAuthToken(adminCol2.getEmail(), password); + + // define authorizations that we know must exists + Authorization adminOfComAToItemA = new Authorization(adminComA, canManageVersionsFeature, itemRestA); + Authorization adminOfCol1ToItemA = new Authorization(adminCol1, canManageVersionsFeature, itemRestA); + + // define authorization that we know not exists + Authorization adminOfComBToItemA = new Authorization(adminComB, canManageVersionsFeature, itemRestA); + Authorization adminOfCol2ToItemA = new Authorization(adminCol2, canManageVersionsFeature, itemRestA); + + getClient(tokenAdminComA).perform(get("/api/authz/authorizations/" + adminOfComAToItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfComAToItemA)))); + + getClient(tokenAdminCol1).perform(get("/api/authz/authorizations/" + adminOfCol1ToItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminOfCol1ToItemA)))); + + getClient(tokenAdminComB).perform(get("/api/authz/authorizations/" + adminOfComBToItemA.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenAdminCol2).perform(get("/api/authz/authorizations/" + adminOfCol2ToItemA.getID())) + .andExpect(status().isNotFound()); + + configurationService.setProperty("versioning.block.entity", ""); } } \ No newline at end of file From 30e1b076f0eed37a76a76d4c412133c913c4ca7c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 16 Sep 2021 12:35:19 +0200 Subject: [PATCH 0274/1254] implemented management of entity type in item versioning --- .../impl/CanCreateVersionFeature.java | 9 ++++++ .../impl/CanDeleteVersionFeature.java | 16 ++++++++++ .../impl/CanEditVersionFeature.java | 15 +++++++-- .../impl/CanManageVersionsFeature.java | 32 ++++++++++--------- .../repository/VersionRestRepository.java | 13 +++++++- 5 files changed, 67 insertions(+), 18 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java index a8ca12f170..7c5645afa6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.Objects; import java.util.UUID; +import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; import org.dspace.app.rest.model.BaseObjectRest; @@ -54,6 +55,14 @@ public class CanCreateVersionFeature implements AuthorizationFeature { } Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); if (Objects.nonNull(item)) { + String stringBlockEntity = configurationService.getProperty("versioning.block.entity"); + boolean isBlockEntity = StringUtils.isNotBlank(stringBlockEntity) ? + Boolean.valueOf(stringBlockEntity) : true; + boolean hasEntityType = StringUtils.isNotBlank(itemService. + getMetadataFirstValue(item, "dspace", "entity", "type", Item.ANY)); + if (isBlockEntity && hasEntityType) { + return false; + } if (authorizeService.isAdmin(context, item)) { return true; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java index 388a1edf9b..c9aed2ebd6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java @@ -9,13 +9,17 @@ package org.dspace.app.rest.authorization.impl; import java.sql.SQLException; import java.util.Objects; +import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.VersionRest; import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.service.VersioningService; import org.springframework.beans.factory.annotation.Autowired; @@ -32,10 +36,14 @@ import org.springframework.stereotype.Component; description = "It can be used to verify if the user can delete a version of an Item") public class CanDeleteVersionFeature extends DeleteFeature { + @Autowired + private ItemService itemService; @Autowired private ItemConverter itemConverter; @Autowired private VersioningService versioningService; + @Autowired + private ConfigurationService configurationService; public static final String NAME = "canDeleteVersion"; @@ -46,6 +54,14 @@ public class CanDeleteVersionFeature extends DeleteFeature { Version version = versioningService.getVersion(context, ((VersionRest)object).getId()); if (Objects.nonNull(version) && Objects.nonNull(version.getItem())) { ItemRest itemRest = itemConverter.convert(version.getItem(), DefaultProjection.DEFAULT); + String stringBlockEntity = configurationService.getProperty("versioning.block.entity"); + boolean isBlockEntity = StringUtils.isNotBlank(stringBlockEntity) ? + Boolean.valueOf(stringBlockEntity) : true; + boolean hasEntityType = StringUtils.isNotBlank( + itemService.getMetadataFirstValue(version.getItem(), "dspace", "entity", "type", Item.ANY)); + if (isBlockEntity && hasEntityType) { + return false; + } return super.isAuthorized(context, itemRest); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java index 1f45df5338..389e03b9bd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.authorization.impl; import java.sql.SQLException; import java.util.Objects; +import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; import org.dspace.app.rest.model.BaseObjectRest; @@ -18,6 +19,7 @@ import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.service.VersioningService; import org.springframework.beans.factory.annotation.Autowired; @@ -42,6 +44,8 @@ public class CanEditVersionFeature implements AuthorizationFeature { private AuthorizeService authorizeService; @Autowired private VersioningService versioningService; + @Autowired + private ConfigurationService configurationService; @Override @SuppressWarnings("rawtypes") @@ -53,8 +57,15 @@ public class CanEditVersionFeature implements AuthorizationFeature { } Version version = versioningService.getVersion(context, (((VersionRest) object).getId())); if (Objects.nonNull(version) && Objects.nonNull(version.getItem())) { - Item item = itemService.find(context, version.getItem().getID()); - return Objects.nonNull(item) ? authorizeService.isAdmin(context, item) : false; + String stringBlockEntity = configurationService.getProperty("versioning.block.entity"); + boolean isBlockEntity = StringUtils.isNotBlank(stringBlockEntity) ? + Boolean.valueOf(stringBlockEntity) : true; + boolean hasEntityType = StringUtils.isNotBlank( + itemService.getMetadataFirstValue(version.getItem(), "dspace", "entity", "type", Item.ANY)); + if (isBlockEntity && hasEntityType) { + return false; + } + return authorizeService.isAdmin(context, version.getItem()); } } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java index d61832e1e9..c0b47f24a1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.Objects; import java.util.UUID; +import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; import org.dspace.app.rest.model.BaseObjectRest; @@ -18,7 +19,6 @@ import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -37,30 +37,32 @@ public class CanManageVersionsFeature implements AuthorizationFeature { public static final String NAME = "canManageVersions"; @Autowired - private ConfigurationService configurationService; - + private ItemService itemService; @Autowired private AuthorizeService authorizeService; - @Autowired - private ItemService itemService; + private ConfigurationService configurationService; + @Override + @SuppressWarnings("rawtypes") public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof ItemRest) { - EPerson currentUser = context.getCurrentUser(); - if (Objects.isNull(currentUser)) { + if (Objects.isNull(context.getCurrentUser())) { return false; } - if (authorizeService.isAdmin(context)) { - return true; + Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); + if (Objects.nonNull(item)) { + String stringBlockEntity = configurationService.getProperty("versioning.block.entity"); + boolean isBlockEntity = StringUtils.isNotBlank(stringBlockEntity) ? + Boolean.valueOf(stringBlockEntity) : true; + boolean hasEntityType = StringUtils.isNotBlank(itemService. + getMetadataFirstValue(item, "dspace", "entity", "type", Item.ANY)); + if (isBlockEntity && hasEntityType) { + return false; + } + return authorizeService.isAdmin(context, item); } - if (configurationService.getBooleanProperty("versioning.submitterCanCreateNewVersion")) { - Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); - EPerson submitter = item.getSubmitter(); - return Objects.nonNull(submitter) && currentUser.getID().equals(submitter.getID()); - } - } return false; } 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 4983fbc848..2247f4c991 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 @@ -26,6 +26,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -54,6 +55,9 @@ public class VersionRestRepository extends DSpaceRestRepository Date: Thu, 16 Sep 2021 08:41:42 -0400 Subject: [PATCH 0275/1254] Fix incorrect merge. (#3061) --- dspace-api/src/main/java/org/dspace/core/Context.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index f64113a690..00130f1dc3 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -10,7 +10,6 @@ package org.dspace.core; import java.sql.SQLException; import java.util.ArrayList; import java.util.Deque; -import java.util.EmptyStackException; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -310,10 +309,10 @@ public class Context implements AutoCloseable { Boolean previousState; try { previousState = authStateChangeHistory.pop(); - } catch (EmptyStackException ex) { + } catch (NoSuchElementException ex) { log.warn(LogHelper.getHeader(this, "restore_auth_sys_state", - "not previous state info available " - + ex.getLocalizedMessage())); + "not previous state info available: {}"), + ex::getLocalizedMessage); previousState = Boolean.FALSE; } if (log.isDebugEnabled()) { From a5103dbd39fe3b7163aa7587aaea74da7ee5d695 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Thu, 16 Sep 2021 10:00:36 -0400 Subject: [PATCH 0276/1254] Create setup and destory to cleanup items from test file --- .../org/dspace/app/packager/PackagerIT.java | 208 ++++++++---------- 1 file changed, 97 insertions(+), 111 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index 262fac3ab9..ff79ae8d39 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -14,6 +14,7 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.sql.SQLException; import java.util.Iterator; import java.util.UUID; import java.util.zip.ZipEntry; @@ -39,6 +40,8 @@ import org.dspace.content.service.WorkspaceItemService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.jdom.Element; +import org.junit.After; +import org.junit.Before; import org.junit.Test; /** @@ -60,116 +63,9 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { protected Item article; File tempFile; - @Test - public void packagerExportUUIDTest() throws Exception { + @Before + public void setup() throws IOException { context.turnOffAuthorisationSystem(); - createTemplate(); - createFiles(); - try { - performExportScript(article.getHandle(), tempFile); - assertTrue(tempFile.length() > 0); - String idStr = getID(); - assertEquals(idStr, article.getID().toString()); - } finally { - tempFile.delete(); - } - } - - @Test - public void packagerImportUUIDTest() throws Exception { - context.turnOffAuthorisationSystem(); - createTemplate(); - createFiles(); - try { - //Item - performExportScript(article.getHandle(), tempFile); - String idStr = getID(); - itemService.delete(context, article); - performImportScript(tempFile); - Item item = itemService.find(context, UUID.fromString(idStr)); - assertNotNull(item); - } finally { - tempFile.delete(); - } - } - - @Test - public void packagerImportColUUIDTest() throws Exception { - context.turnOffAuthorisationSystem(); - createTemplate(); - createFiles(); - configService.setProperty("upload.temp.dir",tempFile.getParent()); - try { - performExportScript(col1.getHandle(), tempFile); - String idStr = getID(); - collectionService.delete(context, col1); - performImportScript(tempFile); - Collection collection = collectionService.find(context, UUID.fromString(idStr)); - assertNotNull(collection); - } finally { - tempFile.delete(); - } - } - - @Test - public void packagerImportComUUIDTest() throws Exception { - context.turnOffAuthorisationSystem(); - createTemplate(); - createFiles(); - configService.setProperty("upload.temp.dir",tempFile.getParent()); - try { - //Community - performExportScript(child1.getHandle(), tempFile); - String idStr = getID(); - communityService.delete(context, child1); - performImportScript(tempFile); - Community community = communityService.find(context, UUID.fromString(idStr)); - assertNotNull(community); - } finally { - tempFile.delete(); - } - } - - @Test - public void packagerUUIDAlreadyExistTest() throws Exception { - context.turnOffAuthorisationSystem(); - createTemplate(); - createFiles(); - try { - //Item should be overwritten if UUID already Exists - performExportScript(article.getHandle(), tempFile); - performImportScript(tempFile); - Iterator items = itemService.findByCollection(context, col1); - assertEquals(1, Iterators.size(items)); - } finally { - tempFile.delete(); - } - } - - @Test - public void packagerUUIDAlreadyExistWithoutForceTest() throws Exception { - context.turnOffAuthorisationSystem(); - createTemplate(); - createFiles(); - try { - //should fail to restore the item because the uuid already exists. - performExportScript(article.getHandle(), tempFile); - UUID id = article.getID(); - itemService.delete(context, article); - WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, false); - installItemService.installItem(context, workspaceItem, "123456789/100"); - performImportNoForceScript(tempFile); - Iterator items = itemService.findByCollection(context, col1); - Item testItem = items.next(); - assertFalse(items.hasNext()); //check to make sure there is only 1 item - assertEquals("123456789/100", testItem.getHandle()); //check to make sure the item wasn't overwritten as - // it would have the old handle. - } finally { - tempFile.delete(); - } - } - - protected void createTemplate() { parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); @@ -184,10 +80,100 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { .withIssueDate("2017-10-17") .withEntityType("Publication") .build(); + + tempFile = File.createTempFile("packagerExportTest", ".zip"); + context.restoreAuthSystemState(); } - protected void createFiles() throws IOException { - tempFile = File.createTempFile("packagerExportTest", ".zip"); + @After + public void destroy() throws SQLException, IOException { + context.turnOffAuthorisationSystem(); + CommunityBuilder.deleteCommunity(parentCommunity.getID()); + CommunityBuilder.deleteCommunity(child1.getID()); + CommunityBuilder.deleteCommunity(col1.getID()); + CommunityBuilder.deleteCommunity(article.getID()); + CommunityBuilder.deleteCommunity(article.getID()); + tempFile.delete(); + context.restoreAuthSystemState(); + } + + @Test + public void packagerExportUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + + performExportScript(article.getHandle(), tempFile); + assertTrue(tempFile.length() > 0); + String idStr = getID(); + assertEquals(idStr, article.getID().toString()); + } + + @Test + public void packagerImportUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //Item + performExportScript(article.getHandle(), tempFile); + String idStr = getID(); + itemService.delete(context, article); + performImportScript(tempFile); + Item item = itemService.find(context, UUID.fromString(idStr)); + assertNotNull(item); + } + + @Test + public void packagerImportColUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + configService.setProperty("upload.temp.dir",tempFile.getParent()); + + performExportScript(col1.getHandle(), tempFile); + String idStr = getID(); + collectionService.delete(context, col1); + performImportScript(tempFile); + Collection collection = collectionService.find(context, UUID.fromString(idStr)); + assertNotNull(collection); + } + + @Test + public void packagerImportComUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + configService.setProperty("upload.temp.dir",tempFile.getParent()); + + //Community + performExportScript(child1.getHandle(), tempFile); + String idStr = getID(); + communityService.delete(context, child1); + performImportScript(tempFile); + Community community = communityService.find(context, UUID.fromString(idStr)); + assertNotNull(community); + } + + @Test + public void packagerUUIDAlreadyExistTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //Item should be overwritten if UUID already Exists + performExportScript(article.getHandle(), tempFile); + performImportScript(tempFile); + Iterator items = itemService.findByCollection(context, col1); + assertEquals(1, Iterators.size(items)); + } + + @Test + public void packagerUUIDAlreadyExistWithoutForceTest() throws Exception { + context.turnOffAuthorisationSystem(); + //should fail to restore the item because the uuid already exists. + performExportScript(article.getHandle(), tempFile); + UUID id = article.getID(); + itemService.delete(context, article); + WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, false); + installItemService.installItem(context, workspaceItem, "123456789/100"); + performImportNoForceScript(tempFile); + Iterator items = itemService.findByCollection(context, col1); + Item testItem = items.next(); + assertFalse(items.hasNext()); //check to make sure there is only 1 item + assertEquals("123456789/100", testItem.getHandle()); //check to make sure the item wasn't overwritten as + // it would have the old handle. + itemService.delete(context, testItem); } private String getID() throws IOException, MetadataValidationException { From 6696cb7c9c4c7861cc6f4aa1624a7faa882ace30 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Thu, 16 Sep 2021 10:12:46 -0400 Subject: [PATCH 0277/1254] Make handle something that could never be ran into --- .../src/test/java/org/dspace/app/packager/PackagerIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index ff79ae8d39..d7bdce9279 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -166,12 +166,12 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { UUID id = article.getID(); itemService.delete(context, article); WorkspaceItem workspaceItem = workspaceItemService.create(context, col1, id, false); - installItemService.installItem(context, workspaceItem, "123456789/100"); + installItemService.installItem(context, workspaceItem, "123456789/0100"); performImportNoForceScript(tempFile); Iterator items = itemService.findByCollection(context, col1); Item testItem = items.next(); assertFalse(items.hasNext()); //check to make sure there is only 1 item - assertEquals("123456789/100", testItem.getHandle()); //check to make sure the item wasn't overwritten as + assertEquals("123456789/0100", testItem.getHandle()); //check to make sure the item wasn't overwritten as // it would have the old handle. itemService.delete(context, testItem); } From f80ad97d95c8ff6b473148d887982ac4bdf9caf0 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 16 Sep 2021 17:01:28 +0200 Subject: [PATCH 0278/1254] added test to show bug in canDeleteVersion feature --- .../app/rest/VersionRestRepositoryIT.java | 89 +++++++++++++++++++ 1 file changed, 89 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 be84e2ace9..397562f368 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 @@ -23,12 +23,20 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; +import org.dspace.app.rest.authorization.Authorization; +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureService; +import org.dspace.app.rest.authorization.impl.CanDeleteVersionFeature; +import org.dspace.app.rest.converter.VersionConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.VersionMatcher; +import org.dspace.app.rest.model.VersionRest; 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.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; @@ -47,6 +55,7 @@ import org.dspace.content.service.WorkspaceItemService; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; +import org.dspace.versioning.service.VersioningService; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; import org.junit.Before; @@ -66,6 +75,12 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { private Version version; + @Autowired + private VersioningService versioningService; + + @Autowired + private VersionConverter versionConverter; + @Autowired private ConfigurationService configurationService; @@ -75,6 +90,9 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private WorkspaceItemService workspaceItemService; + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + @Before public void setup() throws SQLException, AuthorizeException { //disable file upload mandatory @@ -1304,4 +1322,75 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isNotFound()); } + @Test + public void checkDeleteOfMultipleVersionWithAuthorizationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + AuthorizationFeature canDeleteVersionFeature = authorizationFeatureService.find(CanDeleteVersionFeature.NAME); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-03-20") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version v2 = VersionBuilder.createVersion(context, item, "test").build(); + Item lastVersionItem = v2.getItem(); + Version v1 = versioningService.getVersion(context, item); + VersionRest versionRest = versionConverter.convert(v1, DefaultProjection.DEFAULT); + Authorization admin2ItemA = new Authorization(admin, canDeleteVersionFeature, versionRest); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + String adminToken = getAuthToken(admin.getEmail(), password); + + // the first version item is archived + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))); + + // item that linked last version is not archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + // retrieve the workspace item + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/item") + .param("uuid", String.valueOf(lastVersionItem.getID()))) + .andExpect(status().isOk()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submit the workspaceitem to complete the deposit + getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + idRef.get()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // now the last version item is archived + getClient(adminToken).perform(get("/api/core/items/" + lastVersionItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))); + + // the first version item is not archived, but is not in ProgressSubmission + getClient(adminToken).perform(get("/api/core/items/" + item.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + // check authorization that first version is possible to delete + getClient(adminToken).perform(get("/api/authz/authorizations/" + admin2ItemA.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(AuthorizationMatcher.matchAuthorization(admin2ItemA)))); + } + } \ No newline at end of file From 87040d154f236fc5b4c4a4054e9495bb9b5c7a1d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 16 Sep 2021 17:04:18 +0200 Subject: [PATCH 0279/1254] fix canDeleteVersion feature bug and removed unused dependency --- .../authorization/AuthorizeServiceRestUtil.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizeServiceRestUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizeServiceRestUtil.java index 6b34479ec0..7d3936c36d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizeServiceRestUtil.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizeServiceRestUtil.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.authorization; import java.sql.SQLException; +import java.util.Objects; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.security.DSpaceRestPermission; @@ -15,8 +16,7 @@ import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.springframework.beans.factory.annotation.Autowired; @@ -27,12 +27,13 @@ import org.springframework.stereotype.Component; */ @Component public class AuthorizeServiceRestUtil { + + @Autowired + private ItemService itemService; @Autowired private AuthorizeService authorizeService; @Autowired private Utils utils; - @Autowired - private ContentServiceFactory contentServiceFactory; /** * Checks that the specified eperson can perform the given action on the rest given object. @@ -52,16 +53,14 @@ public class AuthorizeServiceRestUtil { return false; } - DSpaceObjectService dSpaceObjectService = - contentServiceFactory.getDSpaceObjectService(dSpaceObject.getType()); - EPerson ePerson = context.getCurrentUser(); // If the item is still inprogress we can process here only the READ permission. // Other actions need to be evaluated against the wrapper object (workspace or workflow item) if (dSpaceObject instanceof Item) { + Item item = (Item) dSpaceObject; if (!DSpaceRestPermission.READ.equals(dSpaceRestPermission) - && !((Item) dSpaceObject).isArchived() && !((Item) dSpaceObject).isWithdrawn()) { + && (itemService.isInProgressSubmission(context, item) || Objects.nonNull(item.getTemplateItemOf()))) { return false; } } From 9f463239d2eed66ad78d4ec5db31cb523332e989 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 16 Sep 2021 13:23:48 -0400 Subject: [PATCH 0280/1254] Address new LGTM alert for removed parameter. (#3061) --- dspace-api/src/main/java/org/dspace/search/Harvest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/search/Harvest.java b/dspace-api/src/main/java/org/dspace/search/Harvest.java index b0bafd367f..773d45a6ab 100644 --- a/dspace-api/src/main/java/org/dspace/search/Harvest.java +++ b/dspace-api/src/main/java/org/dspace/search/Harvest.java @@ -42,7 +42,6 @@ import org.dspace.handle.service.HandleService; * withdrawn within a particular range of dates. * * @author Robert Tansley - * @version $Revision$ */ public class Harvest { /** @@ -229,7 +228,6 @@ public class Harvest { /** * Fill out the containers field of the HarvestedItemInfo object * - * @param context DSpace context * @param itemInfo HarvestedItemInfo object to fill out * @throws SQLException if database error */ From 5ff3bed4e24d360f623470bc233db7744db228e1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 16 Sep 2021 14:06:14 -0700 Subject: [PATCH 0281/1254] Fixed the canvas identifier in search annotions. --- .../app/rest/iiif/service/WordHighlightSolrSearch.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java index e7947453e1..b30a2ca14f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java @@ -53,7 +53,6 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { private String endpoint; private String manifestId; - private boolean validationEnabled; @Autowired IIIFUtils utils; @@ -251,13 +250,14 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { * @return a single annotation object that contains word highlights on a single page (canvas) */ private AnnotationGenerator createSearchResultAnnotation(String params, String text, String pageId, UUID uuid) { - String identifier = this.endpoint + uuid + "/annot/" + pageId + "-" + params; + String annotationIdentifier = this.endpoint + uuid + "/annot/" + pageId + "-" + params; + String canvasIdentifier = this.endpoint + uuid + "/canvas/" + pageId + "#xywh=" + params; contentAsText.setText(text); - CanvasGenerator canvas = new CanvasGenerator().setIdentifier(identifier); + CanvasGenerator canvas = new CanvasGenerator().setIdentifier(canvasIdentifier); AnnotationGenerator annotationGenerator = new AnnotationGenerator() .setMotivation(AnnotationGenerator.PAINTING) - .setIdentifier(identifier) + .setIdentifier(annotationIdentifier) .setOnCanvas(canvas) .setResource(contentAsText) .setWithin(getWithinManifest()); From 6f27b82aa6b6ecc682281eaf435193d375f74035 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 16 Sep 2021 16:19:38 -0500 Subject: [PATCH 0282/1254] Upgrade to latest PostgreSQL driver 42.2.x and Flyway 6.x --- dspace-api/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 81303d3785..a55c2563ee 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -743,7 +743,7 @@ org.flywaydb flyway-core - 6.5.5 + 6.5.7 diff --git a/pom.xml b/pom.xml index db14434e1c..e964a30c76 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.2.2.RELEASE 5.4.10.Final 6.0.18.Final - 42.2.9 + 42.2.23 8.8.1 1.2.22 From facadf47df040fbbe2d4a3fa85872d5e90fa038e Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 16 Sep 2021 14:28:43 -0700 Subject: [PATCH 0283/1254] Moved default iiif configuration to iiif.cfg. --- dspace/config/dspace.cfg | 22 ---------------------- dspace/config/modules/iiif.cfg | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 30a939a667..2929839603 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1511,28 +1511,6 @@ log.report.dir = ${dspace.dir}/log # Take this key (just the UA-XXXXXX-X part) and place it here in this parameter. # google.analytics.key=UA-XXXXXX-X -#### IIIF CONFIGURATION #### -# Base URL of the DSpace iiif API endpoint. -iiif.url = ${dspace.server.url}/iiif/ - -# Base URL of the IIIF image server. -iiif.image.server = http://localhost:8182/iiif/2/ - -# Base URL of the solr search index (IIIF Search API). -iiif.search.url = http://localhost:8983/solr/word_highlighting - -iiif.search.plugin = WordHighlightSolrSearch - -iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams - -# Sets the viewing hint. Possible values: "paged" or "individuals". -# Typically "paged" is preferred for multi-age documents. Use "individuals" -# if you plan to implement the search api. - -iiif.document.viewing.hint = individuals - -# iiif.logo.image = https://image/url/i.png - #---------------------------------------------------------------# #----------------REQUEST ITEM CONFIGURATION---------------------# #---------------------------------------------------------------# diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index 5f30b672e2..71a205d9b4 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -1,3 +1,28 @@ +#### IIIF CONFIGURATION #### +# Base URL of the DSpace iiif API endpoint. +iiif.url = ${dspace.server.url}/iiif/ + +# Base URL of the IIIF image server. +iiif.image.server = http://localhost:8182/iiif/2/ + +# Base URL of the solr search index (IIIF Search API). +iiif.search.url = ${solr.server}/solr/word_highlighting + +# This is the search service under development currently. +iiif.search.plugin = WordHighlightSolrSearch + +# Required path prefix for bitstream content. +iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams + +# Sets the viewing hint. Possible values: "paged" or "individuals". +# Typically "paged" is preferred for multi-age documents. Use "individuals" +# if you plan to implement the search api. +iiif.document.viewing.hint = individuals + +# Optional image to associate with manifests. A small image that represents +# an individual or organization associated with the resource it is attached to. +# iiif.logo.image = https://image/url/i.png + # Only these origins (client URLs) can successfully communicate with the IIIF API. This # allows XHR requests from remote IIIF clients. Defaults to ${dspace.ui.url} if unspecified # (as the embedded IIIF client must have access to the API). Multiple allowed origin URLs may From bd6fea13185fab37b7eba040951e10e860908c5a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 16 Sep 2021 14:50:42 -0700 Subject: [PATCH 0284/1254] Correction to iiif cors configuration. --- .../java/org/dspace/app/rest/utils/ApplicationConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 149153bad6..8df6caf9b2 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 @@ -45,7 +45,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("${iiif.cors.allow-credentials:true}") - private boolean iiifCAllowCredentials; + private boolean iiifCorsAllowCredentials; // Configured User Interface URL (default: http://localhost:4000) @Value("${dspace.ui.url:http://localhost:4000}") @@ -107,6 +107,6 @@ public class ApplicationConfig { * @return true or false */ public boolean getIiifAllowCredentials() { - return corsAllowCredentials; + return iiifCorsAllowCredentials; } } From ef07aabbe1197c3bff5a8145be936fdddc43ff2b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 20 Sep 2021 09:54:47 +0200 Subject: [PATCH 0285/1254] added tests for findByEntity collection end point --- .../app/rest/CollectionRestRepositoryIT.java | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 09be02484f..2d19b0a4bb 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -14,7 +14,10 @@ import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotEx import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataNotEmpty; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataStringEndsWith; import static org.dspace.core.Constants.WRITE; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; @@ -57,11 +60,13 @@ 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.EntityTypeBuilder; 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.EntityType; import org.dspace.content.Item; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; @@ -3200,4 +3205,162 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.totalElements", is(1))); } + @Test + public void findAuthorizedCollectionsByEntityType() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType journal = EntityTypeBuilder.createEntityTypeBuilder(context, "Journal").build(); + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("SubCommunity A") + .build(); + + Community subCommunityB = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("SubCommunity B") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withEntityType(journal.getLabel()) + .withName("Collection 1") + .withAdminGroup(eperson) + .build(); + + Collection col2 = CollectionBuilder.createCollection(context, subCommunityB) + .withEntityType(journal.getLabel()) + .withName("Collection 2") + .withAdminGroup(eperson) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withEntityType(publication.getLabel()) + .withName("Collection 3") + .withAdminGroup(eperson) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withEntityType(journal.getLabel()) + .withName("Collection 4") + .build(); + + context.restoreAuthSystemState(); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/core/collections/search/findSubmitAuthorizedByEntityType") + .param("entityType", journal.getLabel())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", equalTo(2))) + .andExpect(jsonPath("$._embedded.collections", containsInAnyOrder( + CollectionMatcher.matchCollection(col1), + CollectionMatcher.matchCollection(col2) + ))); + } + + @Test + public void findSubmitAuthorizedByEntityTypeNotFoundTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType journal = EntityTypeBuilder.createEntityTypeBuilder(context, "Journal").build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType(journal.getLabel()) + .withName("Collection 1") + .withAdminGroup(eperson) + .build(); + + context.restoreAuthSystemState(); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(eperson.getEmail(), password); + + getClient(ePersonToken).perform(get("/api/core/collections/search/findSubmitAuthorizedByEntityType") + .param("entityType", "test")) + .andExpect(status().isNotFound()); + + getClient(adminToken).perform(get("/api/core/collections/search/findSubmitAuthorizedByEntityType") + .param("entityType", "test")) + .andExpect(status().isNotFound()); + } + + @Test + public void findAuthorizedCollectionsByEntityTypeEmptyResponseTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + context.restoreAuthSystemState(); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/core/collections/search/findSubmitAuthorizedByEntityType") + .param("entityType", publication.getLabel())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", equalTo(0))); + } + + @Test + public void findAuthorizedCollectionsByEntityAndQueryType() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType journal = EntityTypeBuilder.createEntityTypeBuilder(context, "Journal").build(); + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community subCommunityA = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("SubCommunity A") + .build(); + + Community subCommunityB = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("SubCommunity B") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) + .withEntityType(journal.getLabel()) + .withName("Thesis Collection") + .withAdminGroup(eperson) + .build(); + + CollectionBuilder.createCollection(context, subCommunityB) + .withEntityType(journal.getLabel()) + .withName("Work Collection") + .withAdminGroup(eperson) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withEntityType(publication.getLabel()) + .withName("Thesis") + .withAdminGroup(eperson) + .build(); + + CollectionBuilder.createCollection(context, subCommunityA) + .withEntityType(journal.getLabel()) + .withName("Collection 1") + .build(); + + context.restoreAuthSystemState(); + + String ePersonToken = getAuthToken(eperson.getEmail(), password); + getClient(ePersonToken).perform(get("/api/core/collections/search/findSubmitAuthorizedByEntityType") + .param("entityType", journal.getLabel()) + .param("query", "Thesis")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", equalTo(1))) + .andExpect(jsonPath("$._embedded.collections", contains( + CollectionMatcher.matchCollection(col1) + ))); + } + } From 25cfa7c750e120683fcb74568b81777ada45766e Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 20 Sep 2021 10:00:30 +0200 Subject: [PATCH 0286/1254] implemented findByEntityType collection end point --- .../dspace/content/CollectionServiceImpl.java | 85 +++++++++++++++++++ .../content/service/CollectionService.java | 41 +++++++++ .../CollectionIndexFactoryImpl.java | 2 + .../org/dspace/builder/CollectionBuilder.java | 4 + .../repository/CollectionRestRepository.java | 27 ++++++ dspace/solr/search/conf/schema.xml | 3 + 6 files changed, 162 insertions(+) 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 96ba571803..5b5d95ad87 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -992,4 +992,89 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i DiscoverResult resp = searchService.search(context, discoverQuery); return resp; } + + /** + * Finds all Indexed Collections where the current user has submit rights. If the user is an Admin, + * this is all Indexed Collections. Otherwise, it includes those collections where + * an indexed "submit" policy lists either the eperson or one of the eperson's groups + * + * @param context DSpace context + * @param discoverQuery + * @param entityType limit the returned collection to those related to given entity type + * @param community parent community, could be null + * @param q limit the returned collection to those with metadata values matching the query + * terms. The terms are used to make also a prefix query on SOLR + * so it can be used to implement an autosuggest feature over the collection name + * @return discovery search result objects + * @throws SQLException if something goes wrong + * @throws SearchServiceException if search error + */ + private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQuery discoverQuery, + String entityType, Community community, String q) + throws SQLException, SearchServiceException { + + StringBuilder query = new StringBuilder(); + EPerson currentUser = context.getCurrentUser(); + if (!authorizeService.isAdmin(context)) { + String userId = ""; + if (currentUser != null) { + userId = currentUser.getID().toString(); + } + query.append("submit:(e").append(userId); + + Set groups = groupService.allMemberGroupsSet(context, currentUser); + for (Group group : groups) { + query.append(" OR g").append(group.getID()); + } + query.append(")"); + discoverQuery.addFilterQueries(query.toString()); + } + StringBuilder buildFilter = new StringBuilder(); + if (community != null) { + buildFilter.append("location.comm:").append(community.getID().toString()); + } + if (StringUtils.isNotBlank(entityType)) { + if (buildFilter.length() > 0) { + buildFilter.append(" AND "); + } + buildFilter.append("dspace.entity.type:").append(entityType); + } + if (StringUtils.isNotBlank(q)) { + StringBuilder buildQuery = new StringBuilder(); + String escapedQuery = ClientUtils.escapeQueryChars(q); + buildQuery.append(escapedQuery).append(" OR ").append(escapedQuery).append("*"); + discoverQuery.setQuery(buildQuery.toString()); + } + discoverQuery.addFilterQueries(buildFilter.toString()); + DiscoverResult resp = searchService.search(context, discoverQuery); + return resp; + } + + @Override + public List findCollectionsWithSubmit(String q, Context context, Community community, String entityType, + int offset, int limit) throws SQLException, SearchServiceException { + List collections = new ArrayList<>(); + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); + discoverQuery.setStart(offset); + discoverQuery.setMaxResults(limit); + DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, + entityType, community, q); + for (IndexableObject solrCollections : resp.getIndexableObjects()) { + Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); + collections.add(c); + } + return collections; + } + + @Override + public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) + throws SQLException, SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setMaxResults(0); + discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); + DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, entityType, community, q); + return (int) resp.getTotalSearchResults(); + } + } 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 67d45939d5..5c64ed532b 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 @@ -344,6 +344,27 @@ public interface CollectionService Group createDefaultReadGroup(Context context, Collection collection, String typeOfGroupString, int defaultRead) throws SQLException, AuthorizeException; + /** + * Returns Collections for which the current user has 'submit' privileges. + * NOTE: for better performance, this method retrieves its results from an + * index (cache) and does not query the database directly. + * This means that results may be stale or outdated until DS-4524 is resolved" + * + * @param q limit the returned collection to those with metadata values matching the query terms. + * The terms are used to make also a prefix query on SOLR so it can be used to implement + * an autosuggest feature over the collection name + * @param context DSpace Context + * @param community parent community + * @param entityType limit the returned collection to those related to given entity type + * @param offset the position of the first result to return + * @param limit paging limit + * @return discovery search result objects + * @throws SQLException if something goes wrong + * @throws SearchServiceException if search error + */ + public List findCollectionsWithSubmit(String q, Context context, Community community, + String entityType, int offset, int limit) throws SQLException, SearchServiceException; + /** * Returns Collections for which the current user has 'submit' privileges. * NOTE: for better performance, this method retrieves its results from an @@ -381,4 +402,24 @@ public interface CollectionService */ public int countCollectionsWithSubmit(String q, Context context, Community community) throws SQLException, SearchServiceException; + + /** + * Counts the number of Collection for which the current user has 'submit' privileges. + * NOTE: for better performance, this method retrieves its results from an index (cache) + * and does not query the database directly. + * This means that results may be stale or outdated until DS-4524 is resolved." + * + * @param q limit the returned collection to those with metadata values matching the query terms. + * The terms are used to make also a prefix query on SOLR so it can be used to implement + * an autosuggest feature over the collection name + * @param context DSpace Context + * @param community parent community + * @param entityType limit the returned collection to those related to given entity type + * @return total collections found + * @throws SQLException if something goes wrong + * @throws SearchServiceException if search error + */ + public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType) + throws SQLException, SearchServiceException; + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java index 5130be6cd7..f1bbcb8994 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java @@ -111,6 +111,7 @@ public class CollectionIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl toIgnoreMetadataFields = SearchUtils.getIgnoredMetadataFields(collection.getType()); addContainerMetadataField(doc, highlightedMetadataFields, toIgnoreMetadataFields, "dc.description", @@ -125,6 +126,7 @@ public class CollectionIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl { return setMetadataSingleValue(collection, MetadataSchemaEnum.DC.getName(), "title", null, name); } + public CollectionBuilder withEntityType(final String entityType) { + return setMetadataSingleValue(collection, "dspace", "entity", "type", entityType); + } + /** * Set the name of the Collection in the given language. * 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 65d4ef4e6b..4a5d9f8e71 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 @@ -41,10 +41,12 @@ import org.dspace.authorize.service.AuthorizeService; 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.service.BitstreamService; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; +import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -112,6 +114,9 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository findSubmitAuthorizedByEntityType( + @Parameter(value = "query") String query, + @Parameter(value = "entityType", required = true) String entityTypeLabel, + Pageable pageable) + throws SearchServiceException { + try { + Context context = obtainContext(); + EntityType entityType = this.entityTypeService.findByEntityType(context, entityTypeLabel); + if (entityType == null) { + throw new ResourceNotFoundException("There was no entityType found with label: " + entityTypeLabel); + } + List collections = cs.findCollectionsWithSubmit(query, context, null, entityTypeLabel, + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getOffset() + pageable.getPageSize())); + int tot = cs.countCollectionsWithSubmit(query, context, null, entityTypeLabel); + return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override @PreAuthorize("hasPermission(#id, 'COLLECTION', 'WRITE')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id, diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index e16e213135..8df40c2728 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -263,6 +263,9 @@ + + + From 1b366f8b7429947eecceb3b7daeec29f03605d64 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 20 Sep 2021 08:55:11 -0400 Subject: [PATCH 0287/1254] Address review comments: missed some isEmpty fixes. [#3061] --- .../org/dspace/app/bulkedit/DSpaceCSV.java | 2 +- .../main/java/org/dspace/core/I18nUtil.java | 4 +- dspace-api/src/test/resources/log4j2-test.xml | 56 ------------------- 3 files changed, 3 insertions(+), 59 deletions(-) delete mode 100644 dspace-api/src/test/resources/log4j2-test.xml diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index 64be0196bc..cbc052b557 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -577,7 +577,7 @@ public class DSpaceCSV implements Serializable { csvLine.add(headings.get(i - 1), null); String[] elements = part.split(escapedValueSeparator); for (String element : elements) { - if ((element != null) && !"".equals(element)) { + if ((element != null) && !element.isEmpty()) { csvLine.add(headings.get(i - 1), element); } } diff --git a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java index 8c101a8f05..a853c3597e 100644 --- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java +++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java @@ -320,11 +320,11 @@ public class I18nUtil { fileType = ""; } - if (!"".equals(locale.getCountry())) { + if (!locale.getCountry().isEmpty()) { fileNameLC = fileName + "_" + locale.getLanguage() + "_" + locale.getCountry(); - if (!"".equals(locale.getVariant())) { + if (!locale.getVariant().isEmpty()) { fileNameLCV = fileName + "_" + locale.getLanguage() + "_" + locale.getCountry() + "_" + locale.getVariant(); } diff --git a/dspace-api/src/test/resources/log4j2-test.xml b/dspace-api/src/test/resources/log4j2-test.xml deleted file mode 100644 index e0cd821912..0000000000 --- a/dspace-api/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - DEBUG - - - INFO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 276fe2297f27997bb9b5c56908789c0a0c151635 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 20 Sep 2021 15:00:15 +0200 Subject: [PATCH 0288/1254] Refactor the IIIFRestRepository to avoid inheritance from DSpaceRestRepository --- .../org/dspace/app/rest/IIIFController.java | 22 +++++-- .../app/rest/ScriptProcessesController.java | 2 +- .../rest/converter/DSpaceObjectConverter.java | 16 +---- ...Repository.java => IIIFServiceFacade.java} | 20 +++---- .../iiif/service/ImageContentService.java | 2 + .../rest/iiif/service/ManifestService.java | 1 + .../AbstractDSpaceRestRepository.java | 44 +------------- .../AdminRestPermissionEvaluatorPlugin.java | 2 +- ...orizeServicePermissionEvaluatorPlugin.java | 2 +- ...MetadataReadPermissionEvaluatorPlugin.java | 2 +- ...imedTaskRestPermissionEvaluatorPlugin.java | 2 +- ...eObjectAdminPermissionEvaluatorPlugin.java | 2 +- .../EPersonRestPermissionEvaluatorPlugin.java | 2 +- .../GroupRestPermissionEvaluatorPlugin.java | 2 +- ...PoolTaskRestPermissionEvaluatorPlugin.java | 2 +- .../ProcessRestPermissionEvaluatorPlugin.java | 2 +- ...uthorizationPermissionEvaluatorPlugin.java | 2 +- ...PolicyAdminPermissionEvalutatorPlugin.java | 2 +- ...cePolicyRestPermissionEvaluatorPlugin.java | 2 +- ...geReportRestPermissionEvaluatorPlugin.java | 2 +- ...nHistoryRestPermissionEvaluatorPlugin.java | 2 +- .../VersionRestPermissionEvaluatorPlugin.java | 2 +- ...WorkflowRestPermissionEvaluatorPlugin.java | 2 +- ...paceItemRestPermissionEvaluatorPlugin.java | 2 +- .../dspace/app/rest/utils/ContextUtil.java | 59 ++++++++++++++++++- ...epositoryIT.java => IIIFControllerIT.java} | 2 +- 26 files changed, 107 insertions(+), 95 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/{IIIFRestRepository.java => IIIFServiceFacade.java} (87%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/{IIIFRestRepositoryIT.java => IIIFControllerIT.java} (99%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java index f999e84bc0..0414419bc1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java @@ -9,7 +9,11 @@ package org.dspace.app.rest; import java.util.UUID; -import org.dspace.app.rest.iiif.IIIFRestRepository; +import org.dspace.app.rest.iiif.IIIFServiceFacade; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.dspace.services.RequestService; +import org.dspace.utils.DSpace; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,7 +30,9 @@ import org.springframework.web.bind.annotation.RestController; public class IIIFController { @Autowired - IIIFRestRepository iiifRestRepository; + IIIFServiceFacade iiifFacade; + + protected RequestService requestService = new DSpace().getRequestService(); /** * The manifest response contains sufficient information for the client to initialize @@ -43,7 +49,8 @@ public class IIIFController { */ @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest") public String findOne(@PathVariable UUID id) { - return iiifRestRepository.getManifest(id); + Context context = ContextUtil.obtainCurrentRequestContext(); + return iiifFacade.getManifest(context, id); } /** @@ -65,7 +72,8 @@ public class IIIFController { @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest/search") public String searchInManifest(@PathVariable UUID id, @RequestParam(name = "q") String query) { - return iiifRestRepository.searchInManifest(id, query); + Context context = ContextUtil.obtainCurrentRequestContext(); + return iiifFacade.searchInManifest(context, id, query); } /** @@ -81,7 +89,8 @@ public class IIIFController { */ @RequestMapping(method = RequestMethod.GET, value = "/{id}/manifest/seeAlso") public String findSeeAlsoList(@PathVariable UUID id) { - return iiifRestRepository.getSeeAlsoAnnotations(id); + Context context = ContextUtil.obtainCurrentRequestContext(); + return iiifFacade.getSeeAlsoAnnotations(context, id); } /** @@ -98,6 +107,7 @@ public class IIIFController { */ @RequestMapping(method = RequestMethod.GET, value = "/{id}/canvas/{cid}") public String findCanvas(@PathVariable UUID id, @PathVariable String cid) { - return iiifRestRepository.getCanvas(id, cid); + Context context = ContextUtil.obtainCurrentRequestContext(); + return iiifFacade.getCanvas(context, id, cid); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java index 5cc956c5b1..1197e13c98 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java @@ -66,7 +66,7 @@ public class ScriptProcessesController { if (log.isTraceEnabled()) { log.trace("Starting Process for Script with name: " + scriptName); } - Context context = ContextUtil.obtainContext(requestService.getCurrentRequest().getServletRequest()); + Context context = ContextUtil.obtainContext(requestService.getCurrentRequest().getHttpServletRequest()); ProcessRest processRest = scriptRestRepository.startProcess(context, scriptName, files); ProcessResource processResource = converter.toResource(processRest); context.complete(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java index 9b44cea067..0c730265ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java @@ -23,7 +23,6 @@ import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; import org.dspace.core.Context; import org.dspace.services.RequestService; -import org.dspace.services.model.Request; import org.springframework.beans.factory.annotation.Autowired; /** @@ -62,7 +61,8 @@ public abstract class DSpaceObjectConverter locales = request.getHttpServletRequest().getLocales(); - if (locales != null) { - while (locales.hasMoreElements()) { - Locale current = locales.nextElement(); - if (I18nUtil.isSupportedLocale(current)) { - userLocale = current; - break; - } - } - } - } - if (userLocale == null && context.getCurrentUser() != null) { - String userLanguage = context.getCurrentUser().getLanguage(); - if (userLanguage != null) { - userLocale = new Locale(userLanguage); - } - } - if (userLocale == null) { - return I18nUtil.getDefaultLocale(); - } - supportedLocale = I18nUtil.getSupportedLocale(userLocale); - return supportedLocale; - } - } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java index dd04fa95ee..d0112df48f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java @@ -49,7 +49,7 @@ public class AdminRestPermissionEvaluatorPlugin extends RestObjectPermissionEval //We do not check the "permission" object here because administrators are allowed to do everything Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = null; try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java index 69cbcc3aa3..756e2ac4b5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java @@ -62,7 +62,7 @@ public class AuthorizeServicePermissionEvaluatorPlugin extends RestObjectPermiss } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = null; try { if (targetId != null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java index 5940f1c806..b4b08c668b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java @@ -57,7 +57,7 @@ public class BitstreamMetadataReadPermissionEvaluatorPlugin extends RestObjectPe Object permission) { if (permission.toString().equalsIgnoreCase(METADATA_READ_PERMISSION) && targetId != null) { Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); try { UUID dsoUuid = UUID.fromString(targetId.toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java index b7e2514722..f900fe0f59 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ClaimedTaskRestPermissionEvaluatorPlugin.java @@ -55,7 +55,7 @@ public class ClaimedTaskRestPermissionEvaluatorPlugin extends RestObjectPermissi } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = null; try { ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceObjectAdminPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceObjectAdminPermissionEvaluatorPlugin.java index ef1f75f65c..5420a6e7dc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceObjectAdminPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceObjectAdminPermissionEvaluatorPlugin.java @@ -57,7 +57,7 @@ public class DSpaceObjectAdminPermissionEvaluatorPlugin extends RestObjectPermis } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); try { UUID dsoUuid = UUID.fromString(targetId.toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java index 27ea0faa5b..0e208a68a2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java @@ -63,7 +63,7 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = null; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java index 971acc320c..6f168efc91 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java @@ -61,7 +61,7 @@ public class GroupRestPermissionEvaluatorPlugin extends RestObjectPermissionEval } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = context.getCurrentUser(); try { UUID dsoId = UUID.fromString(targetId.toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java index d369cffcf9..b1ea6b5e8c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/PoolTaskRestPermissionEvaluatorPlugin.java @@ -57,7 +57,7 @@ public class PoolTaskRestPermissionEvaluatorPlugin extends RestObjectPermissionE } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = null; try { ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java index aee8bab8c2..94d9694ec4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java @@ -53,7 +53,7 @@ public class ProcessRestPermissionEvaluatorPlugin extends RestObjectPermissionEv } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); try { int processId = Integer.parseInt(targetId.toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ReadAuthorizationPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ReadAuthorizationPermissionEvaluatorPlugin.java index 7c24be3e1c..88670c89fe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ReadAuthorizationPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ReadAuthorizationPermissionEvaluatorPlugin.java @@ -56,7 +56,7 @@ public class ReadAuthorizationPermissionEvaluatorPlugin extends RestObjectPermis } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); try { // admin can always access if (authorizeService.isAdmin(context)) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java index 1740606eb1..0b49d87396 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyAdminPermissionEvalutatorPlugin.java @@ -61,7 +61,7 @@ public class ResourcePolicyAdminPermissionEvalutatorPlugin extends RestObjectPer } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); try { Integer resourcePolicyID = Integer.parseInt(targetId.toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java index 8164063ec1..bf7ce3b53f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResourcePolicyRestPermissionEvaluatorPlugin.java @@ -62,7 +62,7 @@ public class ResourcePolicyRestPermissionEvaluatorPlugin extends RestObjectPermi } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = null; try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java index dfe02c9fbd..c6d080f533 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java @@ -62,7 +62,7 @@ public class UsageReportRestPermissionEvaluatorPlugin extends RestObjectPermissi if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME, targetType) || StringUtils.equalsIgnoreCase(UsageReportRest.NAME + "search", targetType)) { Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); UUID uuidObject = null; if (targetId != null) { if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME, targetType)) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionHistoryRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionHistoryRestPermissionEvaluatorPlugin.java index 79586736b4..d69f6a21ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionHistoryRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionHistoryRestPermissionEvaluatorPlugin.java @@ -53,7 +53,7 @@ public class VersionHistoryRestPermissionEvaluatorPlugin extends RestObjectPermi } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); try { if (configurationService.getBooleanProperty("versioning.item.history.view.admin") diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java index 9b98718120..17f98a498a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPermissionEvaluatorPlugin.java @@ -58,7 +58,7 @@ public class VersionRestPermissionEvaluatorPlugin extends RestObjectPermissionEv } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); try { int versionId = Integer.parseInt(targetId.toString()); 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 a4c452d9c3..137b59fda5 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 @@ -68,7 +68,7 @@ public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionE } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = null; try { ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal()); 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 be4f2e74e7..2084805662 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 @@ -56,7 +56,7 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis } Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getServletRequest()); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); EPerson ePerson = null; WorkspaceItem witem = null; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java index 4d746628c1..be8f49bad2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java @@ -8,11 +8,19 @@ package org.dspace.app.rest.utils; import java.sql.SQLException; +import java.util.Enumeration; +import java.util.Locale; import javax.servlet.ServletException; import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; +import org.dspace.core.I18nUtil; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.dspace.utils.DSpace; /** * Miscellaneous UI utility methods methods for managing DSpace context. @@ -60,12 +68,14 @@ public class ContextUtil { * @param request the servlet request object * @return a context object */ - public static Context obtainContext(ServletRequest request) { + public static Context obtainContext(HttpServletRequest request) { Context context = (Context) request.getAttribute(DSPACE_CONTEXT); if (context == null) { try { context = ContextUtil.initializeContext(); + Locale currentLocale = getLocale(context, request); + context.setCurrentLocale(currentLocale); } catch (SQLException e) { log.error("Unable to initialize context", e); return null; @@ -78,6 +88,53 @@ public class ContextUtil { return context; } + /** + * Shortcut for {@link #obtainContext(Request)} using the {@link RequestService} + * to retrieve the current thread request + * + * @return the DSpace Context associated with the current thread-bound request + */ + public static Context obtainCurrentRequestContext() { + Context context = null; + RequestService requestService = new DSpace().getRequestService(); + Request currentRequest = requestService.getCurrentRequest(); + if (currentRequest != null) { + context = ContextUtil.obtainContext(currentRequest.getHttpServletRequest()); + } + return context; + } + + private static Locale getLocale(Context context, HttpServletRequest request) { + Locale userLocale = null; + Locale supportedLocale = null; + + // Locales requested from client + String locale = request.getHeader("Accept-Language"); + if (StringUtils.isNotBlank(locale)) { + Enumeration locales = request.getLocales(); + if (locales != null) { + while (locales.hasMoreElements()) { + Locale current = locales.nextElement(); + if (I18nUtil.isSupportedLocale(current)) { + userLocale = current; + break; + } + } + } + } + if (userLocale == null && context.getCurrentUser() != null) { + String userLanguage = context.getCurrentUser().getLanguage(); + if (userLanguage != null) { + userLocale = new Locale(userLanguage); + } + } + if (userLocale == null) { + return I18nUtil.getDefaultLocale(); + } + supportedLocale = I18nUtil.getSupportedLocale(userLocale); + return supportedLocale; + } + /** * Initialize a new Context object * diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java index d06eb8d266..b6611b8d83 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java @@ -27,7 +27,7 @@ import org.dspace.content.Item; import org.hamcrest.Matchers; import org.junit.Test; -public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { +public class IIIFControllerIT extends AbstractControllerIntegrationTest { public static final String IIIFBundle = "IIIF"; From 496bb35eea6ce94c75b4777359e1520cb09c66a1 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 20 Sep 2021 16:37:43 +0200 Subject: [PATCH 0289/1254] Implement community feedbacks --- .../versioning/VersionHistoryServiceImpl.java | 26 ++++ .../service/VersionHistoryService.java | 2 + .../impl/CanCreateVersionFeature.java | 4 +- .../impl/CanDeleteVersionFeature.java | 4 +- .../impl/CanEditVersionFeature.java | 4 +- .../impl/CanManageVersionsFeature.java | 4 +- .../converter/VersionHistoryConverter.java | 41 ++---- .../exception/DSpaceForbiddenException.java | 28 ----- ...sionHistoryDraftVersionLinkRepository.java | 4 +- .../repository/VersionRestRepository.java | 15 ++- ...orOfAInprogressSubmissionInformations.java | 3 + .../rest/VersionHistoryRestRepositoryIT.java | 118 +++++++++++++++++- .../app/rest/VersionRestRepositoryIT.java | 41 ++++++ .../CanCreateVersionFeatureIT.java | 3 +- 14 files changed, 211 insertions(+), 86 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceForbiddenException.java diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersionHistoryServiceImpl.java b/dspace-api/src/main/java/org/dspace/versioning/VersionHistoryServiceImpl.java index c7a2f9044c..96c39ac3a8 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersionHistoryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersionHistoryServiceImpl.java @@ -11,11 +11,15 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import org.apache.commons.collections4.CollectionUtils; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; import org.dspace.versioning.dao.VersionHistoryDAO; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; @@ -34,6 +38,12 @@ public class VersionHistoryServiceImpl implements VersionHistoryService { @Autowired(required = true) private VersioningService versioningService; + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private ConfigurationService configurationService; + protected VersionHistoryServiceImpl() { } @@ -210,4 +220,20 @@ public class VersionHistoryServiceImpl implements VersionHistoryService { return versionHistoryDAO.findByItem(context, item); } + @Override + public boolean canSeeDraftVersion(Context context, VersionHistory versionHistory) throws SQLException { + Version version = this.getLatestVersion(context, versionHistory); + if (Objects.nonNull(version)) { + EPerson submitter = version.getItem().getSubmitter(); + boolean isAdmin = authorizeService.isAdmin(context); + boolean canCreateVersion = configurationService + .getBooleanProperty("versioning.submitterCanCreateNewVersion"); + if (!isAdmin && !(canCreateVersion && Objects.equals(submitter, context.getCurrentUser()))) { + return false; + } + return true; + } + return false; + } + } diff --git a/dspace-api/src/main/java/org/dspace/versioning/service/VersionHistoryService.java b/dspace-api/src/main/java/org/dspace/versioning/service/VersionHistoryService.java index e7c88879a5..cb900a0492 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/service/VersionHistoryService.java +++ b/dspace-api/src/main/java/org/dspace/versioning/service/VersionHistoryService.java @@ -67,4 +67,6 @@ public interface VersionHistoryService extends DSpaceCRUDService public void remove(VersionHistory versionHistory, Version version); + public boolean canSeeDraftVersion(Context context, VersionHistory versionHistory) throws SQLException; + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java index 7c5645afa6..4dbc3cdeeb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java @@ -55,9 +55,7 @@ public class CanCreateVersionFeature implements AuthorizationFeature { } Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); if (Objects.nonNull(item)) { - String stringBlockEntity = configurationService.getProperty("versioning.block.entity"); - boolean isBlockEntity = StringUtils.isNotBlank(stringBlockEntity) ? - Boolean.valueOf(stringBlockEntity) : true; + boolean isBlockEntity = configurationService.getBooleanProperty("versioning.block.entity", true); boolean hasEntityType = StringUtils.isNotBlank(itemService. getMetadataFirstValue(item, "dspace", "entity", "type", Item.ANY)); if (isBlockEntity && hasEntityType) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java index c9aed2ebd6..7b47cfe444 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java @@ -54,9 +54,7 @@ public class CanDeleteVersionFeature extends DeleteFeature { Version version = versioningService.getVersion(context, ((VersionRest)object).getId()); if (Objects.nonNull(version) && Objects.nonNull(version.getItem())) { ItemRest itemRest = itemConverter.convert(version.getItem(), DefaultProjection.DEFAULT); - String stringBlockEntity = configurationService.getProperty("versioning.block.entity"); - boolean isBlockEntity = StringUtils.isNotBlank(stringBlockEntity) ? - Boolean.valueOf(stringBlockEntity) : true; + boolean isBlockEntity = configurationService.getBooleanProperty("versioning.block.entity", true); boolean hasEntityType = StringUtils.isNotBlank( itemService.getMetadataFirstValue(version.getItem(), "dspace", "entity", "type", Item.ANY)); if (isBlockEntity && hasEntityType) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java index 389e03b9bd..ed2d852272 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java @@ -57,9 +57,7 @@ public class CanEditVersionFeature implements AuthorizationFeature { } Version version = versioningService.getVersion(context, (((VersionRest) object).getId())); if (Objects.nonNull(version) && Objects.nonNull(version.getItem())) { - String stringBlockEntity = configurationService.getProperty("versioning.block.entity"); - boolean isBlockEntity = StringUtils.isNotBlank(stringBlockEntity) ? - Boolean.valueOf(stringBlockEntity) : true; + boolean isBlockEntity = configurationService.getBooleanProperty("versioning.block.entity", true); boolean hasEntityType = StringUtils.isNotBlank( itemService.getMetadataFirstValue(version.getItem(), "dspace", "entity", "type", Item.ANY)); if (isBlockEntity && hasEntityType) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java index c0b47f24a1..59452accee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java @@ -53,9 +53,7 @@ public class CanManageVersionsFeature implements AuthorizationFeature { } Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); if (Objects.nonNull(item)) { - String stringBlockEntity = configurationService.getProperty("versioning.block.entity"); - boolean isBlockEntity = StringUtils.isNotBlank(stringBlockEntity) ? - Boolean.valueOf(stringBlockEntity) : true; + boolean isBlockEntity = configurationService.getBooleanProperty("versioning.block.entity", true); boolean hasEntityType = StringUtils.isNotBlank(itemService. getMetadataFirstValue(item, "dspace", "entity", "type", Item.ANY)); if (isBlockEntity && hasEntityType) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java index bd6b027063..97c1be7e26 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java @@ -15,13 +15,9 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.projection.Projection; 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.ConfigurationService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; -import org.dspace.versioning.Version; import org.dspace.versioning.VersionHistory; import org.dspace.versioning.service.VersionHistoryService; import org.springframework.beans.factory.annotation.Autowired; @@ -38,12 +34,6 @@ public class VersionHistoryConverter implements DSpaceConverter idRef = new AtomicReference(); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + // retrieve the workspace item + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/search/item") + .param("uuid", String.valueOf(version.getItem().getID()))) + .andExpect(status().isOk()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // submit the workspaceitem to complete the deposit + getClient(tokenAdmin).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + idRef.get()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + vh.getID() + "/draftVersion")) + .andExpect(status().isNoContent()); + } + + @Test + public void findWorkflowItemOfDraftVersionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withWorkflowGroup(1, admin) + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + XmlWorkflowItem witem = WorkflowItemBuilder.createWorkflowItem(context, col) + .withTitle("Workflow Item 1") + .withIssueDate("2017-10-17") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Version version = VersionBuilder.createVersion(context, item, "test").build(); + version.setItem(witem.getItem()); + VersionHistory vh = versionHistoryService.findByItem(context, version.getItem()); + context.turnOffAuthorisationSystem(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + vh.getID() + "/draftVersion")) + .andExpect(jsonPath("$", Matchers.is( + WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workflow Item 1", "2017-10-17", "ExtraEntry")))); + } + @Test public void findWorkspaceItemOfDraftVersionLoggedUserTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -412,7 +528,7 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio } @Test - public void findVersionsOfVersionHistoryCheckPaginationAfterDelitingOfVersionTest() throws Exception { + public void findVersionsOfVersionHistoryCheckPaginationAfterDeletingOfVersionTest() throws Exception { //disable file upload mandatory configurationService.setProperty("webui.submit.upload.required", false); context.turnOffAuthorisationSystem(); 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 397562f368..916aca0198 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 @@ -8,6 +8,7 @@ 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.emptyOrNullString; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -293,6 +294,46 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { } } + @Test + public void createFirstVersionWithoutSummaryTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + String adminToken = getAuthToken(admin.getEmail(), password); + + try { + getClient(adminToken).perform(post("/api/versioning/versions") + .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", emptyOrNullString()), + hasJsonPath("$.submitterName", is("first (admin) last (admin)")), + hasJsonPath("$.type", is("version")) + ))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + VersionBuilder.delete(idRef.get()); + } + } + @Test public void createFirstVersionItemBadRequestTest() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java index 2ee953d332..8a6d836466 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java @@ -409,7 +409,7 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest context.turnOffAuthorisationSystem(); configurationService.setProperty("versioning.submitterCanCreateNewVersion", true); - configurationService.setProperty("versioning.block.entity", ""); + configurationService.setProperty("versioning.block.entity", null); Community rootCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -417,7 +417,6 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest Collection col = CollectionBuilder.createCollection(context, rootCommunity) .withName("Collection 1") - .withAdminGroup(eperson) .withSubmitterGroup(eperson) .build(); From e5b23db673f5cef5396a14d39d63a3b0975b38e6 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 20 Sep 2021 10:54:29 -0400 Subject: [PATCH 0290/1254] Replace now-ignored test log4j.properties files with log4j2-test.xml. [7935] --- .../src/test/resources/log4j.properties | 58 ------------------- dspace-api/src/test/resources/log4j2-test.xml | 56 ++++++++++++++++++ .../src/test/resources/log4j.properties | 7 --- .../src/test/resources/log4j2-test.xml | 16 +++++ 4 files changed, 72 insertions(+), 65 deletions(-) delete mode 100644 dspace-api/src/test/resources/log4j.properties create mode 100644 dspace-api/src/test/resources/log4j2-test.xml delete mode 100644 dspace-services/src/test/resources/log4j.properties create mode 100644 dspace-services/src/test/resources/log4j2-test.xml diff --git a/dspace-api/src/test/resources/log4j.properties b/dspace-api/src/test/resources/log4j.properties deleted file mode 100644 index 2797b7c655..0000000000 --- a/dspace-api/src/test/resources/log4j.properties +++ /dev/null @@ -1,58 +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/ -# -########################################################################### -# -# log4j.properties -# -# -########################################################################### - -# This is a copy of the log4j configuration file for DSpace, to avoid -# getting errors when running tests. - -# Set root category priority to INFO and its only appender to A1. -log4j.rootCategory=INFO, A1 - -# A1 is set to be a ConsoleAppender. -log4j.appender.A1=org.apache.log4j.ConsoleAppender - -# A1 uses PatternLayout. -log4j.appender.A1.layout=org.apache.logging.log4j.PatternLayout -log4j.appender.A1.layout.ConversionPattern=%d %-5p %c @ %m%n - -########################################################################### -# Other settings -########################################################################### - -# Block passwords from being exposed in Axis logs. -# (DEBUG exposes passwords in Basic Auth) -log4j.logger.org.apache.axis.handlers.http.HTTPAuthHandler=INFO - -# Block services logging except on exceptions -log4j.logger.org.dspace.kernel=ERROR -log4j.logger.org.dspace.services=ERROR -log4j.logger.org.dspace.servicemanager=ERROR -log4j.logger.org.dspace.providers=ERROR -log4j.logger.org.dspace.utils=ERROR - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Target=System.out -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n -# -# Root logger option -log4j.rootLogger=INFO, stdout - -# Hibernate logging options (INFO only shows startup messages) -log4j.logger.org.hibernate=INFO - -# For detailed Hibernate logging in Unit Tests, you can enable the following -# setting which logs all JDBC bind parameter runtime arguments. -# This will drastically increase the size of Unit Test logs though. -#log4j.logger.org.hibernate.SQL=DEBUG, A1 -#log4j.logger.org.hibernate.type=TRACE, A1 diff --git a/dspace-api/src/test/resources/log4j2-test.xml b/dspace-api/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..e0cd821912 --- /dev/null +++ b/dspace-api/src/test/resources/log4j2-test.xml @@ -0,0 +1,56 @@ + + + + + + DEBUG + + + INFO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-services/src/test/resources/log4j.properties b/dspace-services/src/test/resources/log4j.properties deleted file mode 100644 index 2f1799c59c..0000000000 --- a/dspace-services/src/test/resources/log4j.properties +++ /dev/null @@ -1,7 +0,0 @@ -log4j.rootCategory=info -log4j.rootLogger=info, stdout - -log4j.appender.stdout=ConsoleAppender -log4j.appender.stdout.layout=PatternLayout -log4j.appender.stdout.layout.ConversionPattern= %p %m [%d] (%F:%L) %n - diff --git a/dspace-services/src/test/resources/log4j2-test.xml b/dspace-services/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..f3c4f2788a --- /dev/null +++ b/dspace-services/src/test/resources/log4j2-test.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + From 09d8981227ccfe046b3d18dc0015e23bbe8ad5fa Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 20 Sep 2021 13:59:29 -0700 Subject: [PATCH 0291/1254] Updated javadoc. --- .../org/dspace/app/rest/iiif/service/util/IIIFUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 03bfe20d48..90f9ecd1fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -286,9 +286,11 @@ public class IIIFUtils { } /** - * Serializes the json response. + * Serializes the resource. This method uses a customized + * object mapper instead of the Spring default to serialize the + * iiif response. * @param resource to be serialized - * @return + * @return json string */ public String asJson(Resource resource) { mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); From ebd2b89272917cfd25af00a793676574494f379d Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Tue, 21 Sep 2021 09:44:42 +1000 Subject: [PATCH 0292/1254] Removed processRelationships property --- .../org/dspace/app/itemimport/ItemImportServiceImpl.java | 6 ------ 1 file changed, 6 deletions(-) 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 9214fc0570..880a59a3ee 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 @@ -172,7 +172,6 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected boolean useWorkflow = false; protected boolean useWorkflowSendEmail = false; protected boolean isQuiet = false; - protected boolean processRelationships = false; //remember which folder item was imported from Map itemFolderMap = null; @@ -2121,9 +2120,4 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea this.isQuiet = isQuiet; } - @Override - public void setProcessRelationships(boolean processRelationships) { - this.processRelationships = processRelationships; - } - } From ed29084ccb7a39aafce601eb440600506b80cc67 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 21 Sep 2021 13:14:14 +0200 Subject: [PATCH 0293/1254] 83661: Port harvest script to scripts and processes --- .../java/org/dspace/app/harvest/Harvest.java | 319 ++++++++---------- .../harvest/HarvestScriptConfiguration.java | 71 ++++ .../impl/RestDSpaceRunnableHandler.java | 4 +- dspace/config/spring/api/scripts.xml | 5 + 4 files changed, 218 insertions(+), 181 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java index 290ed9ea39..fe8b8a4dd1 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java @@ -1,7 +1,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 + * tree and available oncommandLine at * * http://www.dspace.org/license/ */ @@ -13,11 +13,8 @@ import java.util.Iterator; import java.util.List; import java.util.UUID; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; @@ -36,221 +33,187 @@ import org.dspace.harvest.HarvestingException; import org.dspace.harvest.OAIHarvester; import org.dspace.harvest.factory.HarvestServiceFactory; import org.dspace.harvest.service.HarvestedCollectionService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; /** * Test class for harvested collections. * * @author Alexey Maslov */ -public class Harvest { - private static Context context; +public class Harvest extends DSpaceRunnable { - private static final HarvestedCollectionService harvestedCollectionService = - HarvestServiceFactory.getInstance().getHarvestedCollectionService(); - private static final EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - private static final CollectionService collectionService = - ContentServiceFactory.getInstance().getCollectionService(); + private HarvestedCollectionService harvestedCollectionService; + private EPersonService ePersonService; + private CollectionService collectionService; - public static void main(String[] argv) throws Exception { - // create an options object and populate it - CommandLineParser parser = new DefaultParser(); - - Options options = new Options(); - - options.addOption("p", "purge", false, "delete all items in the collection"); - options.addOption("r", "run", false, "run the standard harvest procedure"); - options.addOption("g", "ping", false, "test the OAI server and set"); - options.addOption("s", "setup", false, "Set the collection up for harvesting"); - options.addOption("S", "start", false, "start the harvest loop"); - options.addOption("R", "reset", false, "reset harvest status on all collections"); - options.addOption("P", "purge", false, "purge all harvestable collections"); + private boolean help; + private String command = null; + private String eperson = null; + private String collection = null; + private String oaiSource = null; + private String oaiSetID = null; + private String metadataKey = null; + private int harvestType = 0; - options.addOption("e", "eperson", true, - "eperson"); - options.addOption("c", "collection", true, - "harvesting collection (handle or id)"); - options.addOption("t", "type", true, - "type of harvesting (0 for none)"); - options.addOption("a", "address", true, - "address of the OAI-PMH server"); - options.addOption("i", "oai_set_id", true, - "id of the PMH set representing the harvested collection"); - options.addOption("m", "metadata_format", true, - "the name of the desired metadata format for harvesting, resolved to namespace and " + - "crosswalk in dspace.cfg"); + public HarvestScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager() + .getServiceByName("harvest", HarvestScriptConfiguration.class); + } - options.addOption("h", "help", false, "help"); + public void setup() throws ParseException { + harvestedCollectionService = + HarvestServiceFactory.getInstance().getHarvestedCollectionService(); + ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + collectionService = + ContentServiceFactory.getInstance().getCollectionService(); - CommandLine line = parser.parse(options, argv); - - String command = null; - String eperson = null; - String collection = null; - String oaiSource = null; - String oaiSetID = null; - String metadataKey = null; - int harvestType = 0; - - if (line.hasOption('h')) { - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("Harvest\n", options); - System.out.println("\nPING OAI server: Harvest -g -a oai_source -i oai_set_id"); - System.out.println( - "SETUP a collection for harvesting: Harvest -s -c collection -t harvest_type -a oai_source -i " + - "oai_set_id -m metadata_format"); - System.out.println("RUN harvest once: Harvest -r -e eperson -c collection"); - System.out.println("START harvest scheduler: Harvest -S"); - System.out.println("RESET all harvest status: Harvest -R"); - System.out.println("PURGE a collection of items and settings: Harvest -p -e eperson -c collection"); - System.out.println("PURGE all harvestable collections: Harvest -P -e eperson"); + help = commandLine.hasOption('h'); //TODO copy the old message? - System.exit(0); - } - - if (line.hasOption('s')) { + if (commandLine.hasOption('s')) { command = "config"; } - if (line.hasOption('p')) { + if (commandLine.hasOption('p')) { command = "purge"; } - if (line.hasOption('r')) { + if (commandLine.hasOption('r')) { command = "run"; } - if (line.hasOption('g')) { + if (commandLine.hasOption('g')) { command = "ping"; } - if (line.hasOption('S')) { + if (commandLine.hasOption('S')) { command = "start"; } - if (line.hasOption('R')) { + if (commandLine.hasOption('R')) { command = "reset"; } - if (line.hasOption('P')) { + if (commandLine.hasOption('P')) { command = "purgeAll"; } - if (line.hasOption('e')) { - eperson = line.getOptionValue('e'); + if (commandLine.hasOption('e')) { + eperson = commandLine.getOptionValue('e'); } - if (line.hasOption('c')) { - collection = line.getOptionValue('c'); + if (commandLine.hasOption('c')) { + collection = commandLine.getOptionValue('c'); } - if (line.hasOption('t')) { - harvestType = Integer.parseInt(line.getOptionValue('t')); + if (commandLine.hasOption('t')) { + harvestType = Integer.parseInt(commandLine.getOptionValue('t')); } else { harvestType = 0; } - if (line.hasOption('a')) { - oaiSource = line.getOptionValue('a'); + if (commandLine.hasOption('a')) { + oaiSource = commandLine.getOptionValue('a'); } - if (line.hasOption('i')) { - oaiSetID = line.getOptionValue('i'); + if (commandLine.hasOption('i')) { + oaiSetID = commandLine.getOptionValue('i'); } - if (line.hasOption('m')) { - metadataKey = line.getOptionValue('m'); + if (commandLine.hasOption('m')) { + metadataKey = commandLine.getOptionValue('m'); + } + } + + public void internalRun() throws Exception { + if (help) { + printHelp(); + handler.logInfo("PING OAI server: Harvest -g -a oai_source -i oai_set_id"); + handler.logInfo( + "SETUP a collection for harvesting: Harvest -s -c collection -t harvest_type -a oai_source -i " + + "oai_set_id -m metadata_format"); + handler.logInfo("RUN harvest once: Harvest -r -e eperson -c collection"); + handler.logInfo("START harvest scheduler: Harvest -S"); + handler.logInfo("RESET all harvest status: Harvest -R"); + handler.logInfo("PURGE a collection of items and settings: Harvest -p -e eperson -c collection"); + handler.logInfo("PURGE all harvestable collections: Harvest -P -e eperson"); + + return; } + Context context = new Context(Context.Mode.BATCH_EDIT); - // Instantiate our class - Harvest harvester = new Harvest(); - harvester.context = new Context(Context.Mode.BATCH_EDIT); - - - // Check our options - if (command == null) { - System.out - .println("Error - no parameters specified (run with -h flag for details)"); - System.exit(1); + if (StringUtils.isBlank(command)) { + handler.logError("No parameters specified (run with -h flag for details)"); + throw new UnsupportedOperationException("No command specified"); } else if ("run".equals(command)) { // Run a single harvest cycle on a collection using saved settings. if (collection == null || eperson == null) { - System.out - .println("Error - a target collection and eperson must be provided"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); + handler.logError("A target collection and eperson must be provided (run with -h flag for details)"); + throw new UnsupportedOperationException("A target collection and eperson must be provided"); } - - harvester.runHarvest(collection, eperson); + runHarvest(context, collection, eperson); } else if ("start".equals(command)) { // start the harvest loop startHarvester(); } else if ("reset".equals(command)) { // reset harvesting status - resetHarvesting(); + resetHarvesting(context); } else if ("purgeAll".equals(command)) { // purge all collections that are set up for harvesting (obviously for testing purposes only) if (eperson == null) { - System.out - .println("Error - an eperson must be provided"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); + handler.logError("An eperson must be provided (run with -h flag for details)"); + throw new UnsupportedOperationException("An eperson must be provided"); } List harvestedCollections = harvestedCollectionService.findAll(context); for (HarvestedCollection harvestedCollection : harvestedCollections) { - System.out.println( - "Purging the following collections (deleting items and resetting harvest status): " + - harvestedCollection - .getCollection().getID().toString()); - harvester.purgeCollection(harvestedCollection.getCollection().getID().toString(), eperson); + handler.logInfo( + "Purging the following collections (deleting items and resetting harvest status): " + + harvestedCollection + .getCollection().getID().toString()); + purgeCollection(context, harvestedCollection.getCollection().getID().toString(), eperson); } context.complete(); } else if ("purge".equals(command)) { // Delete all items in a collection. Useful for testing fresh harvests. if (collection == null || eperson == null) { - System.out - .println("Error - a target collection and eperson must be provided"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); + handler.logError("A target collection and eperson must be provided (run with -h flag for details)"); + throw new UnsupportedOperationException("A target collection and eperson must be provided"); } - harvester.purgeCollection(collection, eperson); + purgeCollection(context, collection, eperson); context.complete(); //TODO: implement this... remove all items and remember to unset "last-harvested" settings } else if ("config".equals(command)) { // Configure a collection with the three main settings if (collection == null) { - System.out.println("Error - a target collection must be provided"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); + handler.logError("A target collection must be provided (run with -h flag for details)"); + throw new UnsupportedOperationException("A target collection must be provided"); } if (oaiSource == null || oaiSetID == null) { - System.out.println("Error - both the OAI server address and OAI set id must be specified"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); + handler.logError("Both the OAI server address and OAI set id must be specified (run with -h flag for details)"); + throw new UnsupportedOperationException("Both the OAI server address and OAI set id must be specified"); } if (metadataKey == null) { - System.out - .println("Error - a metadata key (commonly the prefix) must be specified for this collection"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); + handler.logError("A metadata key (commonly the prefix) must be specified for this collection (run with -h flag for details)"); + throw new UnsupportedOperationException("A metadata key (commonly the prefix) must be specified for this collection"); } - harvester.configureCollection(collection, harvestType, oaiSource, oaiSetID, metadataKey); + configureCollection(context, collection, harvestType, oaiSource, oaiSetID, metadataKey); } else if ("ping".equals(command)) { if (oaiSource == null || oaiSetID == null) { - System.out.println("Error - both the OAI server address and OAI set id must be specified"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); + handler.logError("Both the OAI server address and OAI set id must be specified (run with -h flag for details)"); + throw new UnsupportedOperationException("Both the OAI server address and OAI set id must be specified"); } pingResponder(oaiSource, oaiSetID, metadataKey); } else { - System.out.println("Error - your command '" + command + "' was not recoginzed properly"); - System.out.println(" (run with -h flag for details)"); - System.exit(1); + handler.logError("Your command '" + command + "' was not recoginzed properly (run with -h flag for details)"); + throw new UnsupportedOperationException("\"Your command '\" + command + \"' was not recoginzed properly\""); } + + } /* * Resolve the ID into a collection and check to see if its harvesting options are set. If so, return * the collection, if not, bail out. */ - private Collection resolveCollection(String collectionID) { + private Collection resolveCollection(Context context, String collectionID) { DSpaceObject dso; Collection targetCollection = null; @@ -270,14 +233,14 @@ public class Harvest { } } else { // not a handle, try and treat it as an collection database UUID - System.out.println("Looking up by UUID: " + collectionID + ", " + "in context: " + context); + handler.logInfo("Looking up by UUID: " + collectionID + ", " + "in context: " + context); targetCollection = collectionService.find(context, UUID.fromString(collectionID)); } } // was the collection valid? if (targetCollection == null) { - System.out.println("Cannot resolve " + collectionID + " to collection"); - System.exit(1); + handler.logError("Cannot resolve " + collectionID + " to collection"); + throw new UnsupportedOperationException("Cannot resolve " + collectionID + " to collection"); } } catch (SQLException se) { se.printStackTrace(); @@ -287,12 +250,12 @@ public class Harvest { } - private void configureCollection(String collectionID, int type, String oaiSource, String oaiSetId, + private void configureCollection(Context context, String collectionID, int type, String oaiSource, String oaiSetId, String mdConfigId) { - System.out.println("Running: configure collection"); + handler.logInfo("Running: configure collection"); - Collection collection = resolveCollection(collectionID); - System.out.println(collection.getID()); + Collection collection = resolveCollection(context, collectionID); + handler.logInfo(String.valueOf(collection.getID())); try { HarvestedCollection hc = harvestedCollectionService.find(context, collection); @@ -307,9 +270,8 @@ public class Harvest { context.restoreAuthSystemState(); context.complete(); } catch (Exception e) { - System.out.println("Changes could not be committed"); - e.printStackTrace(); - System.exit(1); + handler.logError("Changes could not be committed"); + handler.handleException(e); } finally { if (context != null) { context.restoreAuthSystemState(); @@ -324,10 +286,10 @@ public class Harvest { * @param collectionID * @param email */ - private void purgeCollection(String collectionID, String email) { - System.out.println( - "Purging collection of all items and resetting last_harvested and harvest_message: " + collectionID); - Collection collection = resolveCollection(collectionID); + private void purgeCollection(Context context, String collectionID, String email) { + handler.logInfo( + "Purging collection of all items and resetting last_harvested and harvest_message: " + collectionID); + Collection collection = resolveCollection(context, collectionID); try { EPerson eperson = ePersonService.findByEmail(context, email); @@ -340,7 +302,7 @@ public class Harvest { while (it.hasNext()) { i++; Item item = it.next(); - System.out.println("Deleting: " + item.getHandle()); + handler.logInfo("Deleting: " + item.getHandle()); collectionService.removeItem(context, collection, item); context.uncacheEntity(item);// Dispatch events every 50 items if (i % 50 == 0) { @@ -360,9 +322,8 @@ public class Harvest { context.restoreAuthSystemState(); context.dispatchEvents(); } catch (Exception e) { - System.out.println("Changes could not be committed"); - e.printStackTrace(); - System.exit(1); + handler.logError("Changes could not be committed"); + handler.handleException(e); } finally { context.restoreAuthSystemState(); } @@ -372,30 +333,28 @@ public class Harvest { /** * Run a single harvest cycle on the specified collection under the authorization of the supplied EPerson */ - private void runHarvest(String collectionID, String email) { - System.out.println("Running: a harvest cycle on " + collectionID); + private void runHarvest(Context context, String collectionID, String email) { + handler.logInfo("Running: a harvest cycle on " + collectionID); - System.out.print("Initializing the harvester... "); + handler.logInfo("Initializing the harvester... "); OAIHarvester harvester = null; try { - Collection collection = resolveCollection(collectionID); + Collection collection = resolveCollection(context, collectionID); HarvestedCollection hc = harvestedCollectionService.find(context, collection); harvester = new OAIHarvester(context, collection, hc); - System.out.println("success. "); + handler.logInfo("Initialized the harvester successfully"); } catch (HarvestingException hex) { - System.out.print("failed. "); - System.out.println(hex.getMessage()); + handler.logError("Initializing the harvester failed."); throw new IllegalStateException("Unable to harvest", hex); } catch (SQLException se) { - System.out.print("failed. "); - System.out.println(se.getMessage()); + handler.logError("Initializing the harvester failed."); throw new IllegalStateException("Unable to access database", se); } try { // Harvest will not work for an anonymous user EPerson eperson = ePersonService.findByEmail(context, email); - System.out.println("Harvest started... "); + handler.logInfo("Harvest started... "); context.setCurrentUser(eperson); harvester.runHarvest(); context.complete(); @@ -403,15 +362,15 @@ public class Harvest { throw new IllegalStateException("Failed to run harvester", e); } - System.out.println("Harvest complete. "); + handler.logInfo("Harvest complete. "); } /** * Resets harvest_status and harvest_start_time flags for all collections that have a row in the * harvested_collections table */ - private static void resetHarvesting() { - System.out.print("Resetting harvest status flag on all collections... "); + private void resetHarvesting(Context context) { + handler.logInfo("Resetting harvest status flag on all collections... "); try { List harvestedCollections = harvestedCollectionService.findAll(context); @@ -421,21 +380,21 @@ public class Harvest { harvestedCollection.setHarvestStatus(HarvestedCollection.STATUS_READY); harvestedCollectionService.update(context, harvestedCollection); } - System.out.println("success. "); + handler.logInfo("Reset harvest status flag successfully"); } catch (Exception ex) { - System.out.println("failed. "); - ex.printStackTrace(); + handler.logError("Resetting harvest status flag failed"); + handler.handleException(ex); } } /** * Starts up the harvest scheduler. Terminating this process will stop the scheduler. */ - private static void startHarvester() { + private void startHarvester() { try { - System.out.print("Starting harvest loop... "); + handler.logInfo("Starting harvest loop... "); HarvestServiceFactory.getInstance().getHarvestSchedulingService().startNewScheduler(); - System.out.println("running. "); + handler.logInfo("running. "); } catch (Exception ex) { ex.printStackTrace(); } @@ -448,29 +407,31 @@ public class Harvest { * @param set name of an item set. * @param metadataFormat local prefix name, or null for "dc". */ - private static void pingResponder(String server, String set, String metadataFormat) { + private void pingResponder(String server, String set, String metadataFormat) { List errors; - System.out.print("Testing basic PMH access: "); + handler.logInfo("Testing basic PMH access: "); errors = harvestedCollectionService.verifyOAIharvester(server, set, - (null != metadataFormat) ? metadataFormat : "dc", false); + (null != metadataFormat) ? metadataFormat : "dc", false); if (errors.isEmpty()) { - System.out.println("OK"); + handler.logInfo("OK"); } else { for (String error : errors) { - System.err.println(error); + handler.logError(error); } } - System.out.print("Testing ORE support: "); + handler.logInfo("Testing ORE support: "); errors = harvestedCollectionService.verifyOAIharvester(server, set, - (null != metadataFormat) ? metadataFormat : "dc", true); + (null != metadataFormat) ? metadataFormat : "dc", true); if (errors.isEmpty()) { - System.out.println("OK"); + handler.logInfo("OK"); } else { for (String error : errors) { - System.err.println(error); + handler.logError(error); } } } + + } diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java new file mode 100644 index 0000000000..4784c3f0ae --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.harvest; + +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; + + +public class HarvestScriptConfiguration extends ScriptConfiguration { + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + public boolean isAllowedToExecute(final 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); + } + } + + public Options getOptions() { + Options options = new Options(); + options.addOption("p", "purge", false, "delete all items in the collection"); + options.addOption("r", "run", false, "run the standard harvest procedure"); + options.addOption("g", "ping", false, "test the OAI server and set"); + options.addOption("s", "setup", false, "Set the collection up for harvesting"); + options.addOption("S", "start", false, "start the harvest loop"); + options.addOption("R", "reset", false, "reset harvest status on all collections"); + options.addOption("P", "purge", false, "purge all harvestable collections"); + + options.addOption("e", "eperson", true, + "eperson"); + options.addOption("c", "collection", true, + "harvesting collection (handle or id)"); + options.addOption("t", "type", true, + "type of harvesting (0 for none)"); + options.addOption("a", "address", true, + "address of the OAI-PMH server"); + options.addOption("i", "oai_set_id", true, + "id of the PMH set representing the harvested collection"); + options.addOption("m", "metadata_format", true, + "the name of the desired metadata format for harvesting, resolved to namespace and " + + "crosswalk in dspace.cfg"); + + options.addOption("h", "help", false, "help"); + + return options; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java index 8f56513749..01d4aced91 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java @@ -207,8 +207,8 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { HelpFormatter formatter = new HelpFormatter(); StringWriter out = new StringWriter(); PrintWriter pw = new PrintWriter(out); - - formatter.printUsage(pw, 1000, name, options); + formatter.printHelp(pw, 1000, name, null, options, formatter.getLeftPadding(), formatter.getDescPadding(), + null, false); pw.flush(); String helpString = out.toString(); diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 52467dabde..a4e91b2fd9 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -40,4 +40,9 @@ + + + + + From 189ea97ffba72657d14cdf9c69782704cd94da46 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 21 Sep 2021 14:47:33 +0200 Subject: [PATCH 0294/1254] 83661: Fix license issues --- .../java/org/dspace/app/harvest/Harvest.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java index fe8b8a4dd1..bbc54d49e4 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java @@ -1,7 +1,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 oncommandLine at + * tree and available online at * * http://www.dspace.org/license/ */ @@ -185,24 +185,30 @@ public class Harvest extends DSpaceRunnable { throw new UnsupportedOperationException("A target collection must be provided"); } if (oaiSource == null || oaiSetID == null) { - handler.logError("Both the OAI server address and OAI set id must be specified (run with -h flag for details)"); + handler.logError( + "Both the OAI server address and OAI set id must be specified (run with -h flag for details)"); throw new UnsupportedOperationException("Both the OAI server address and OAI set id must be specified"); } if (metadataKey == null) { - handler.logError("A metadata key (commonly the prefix) must be specified for this collection (run with -h flag for details)"); - throw new UnsupportedOperationException("A metadata key (commonly the prefix) must be specified for this collection"); + handler.logError( + "A metadata key (commonly the prefix) must be specified for this collection (run with -h flag" + + " for details)"); + throw new UnsupportedOperationException( + "A metadata key (commonly the prefix) must be specified for this collection"); } configureCollection(context, collection, harvestType, oaiSource, oaiSetID, metadataKey); } else if ("ping".equals(command)) { if (oaiSource == null || oaiSetID == null) { - handler.logError("Both the OAI server address and OAI set id must be specified (run with -h flag for details)"); + handler.logError( + "Both the OAI server address and OAI set id must be specified (run with -h flag for details)"); throw new UnsupportedOperationException("Both the OAI server address and OAI set id must be specified"); } pingResponder(oaiSource, oaiSetID, metadataKey); } else { - handler.logError("Your command '" + command + "' was not recoginzed properly (run with -h flag for details)"); + handler.logError( + "Your command '" + command + "' was not recoginzed properly (run with -h flag for details)"); throw new UnsupportedOperationException("\"Your command '\" + command + \"' was not recoginzed properly\""); } From 6433df2d9e4b1a27b7d49b378870d4c6f7a0243c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 21 Sep 2021 17:33:45 +0200 Subject: [PATCH 0295/1254] implemented findSubmitAuthorizedByCommunityAndEntityType end point --- .../dspace/content/CollectionServiceImpl.java | 2 +- .../repository/CollectionRestRepository.java | 30 ++- .../app/rest/CollectionRestRepositoryIT.java | 180 ++++++++++++++++++ dspace/solr/search/conf/schema.xml | 3 +- 4 files changed, 212 insertions(+), 3 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 5b5d95ad87..2d9fabfdf7 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1037,7 +1037,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i if (buildFilter.length() > 0) { buildFilter.append(" AND "); } - buildFilter.append("dspace.entity.type:").append(entityType); + buildFilter.append("search.entitytype:").append(entityType); } if (StringUtils.isNotBlank(q)) { StringBuilder buildQuery = new StringBuilder(); 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 4a5d9f8e71..36c53fd33c 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 @@ -11,6 +11,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.SortedMap; import java.util.UUID; import javax.servlet.ServletInputStream; @@ -235,7 +236,7 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository collections = cs.findCollectionsWithSubmit(query, context, null, entityTypeLabel, Math.toIntExact(pageable.getOffset()), - Math.toIntExact(pageable.getOffset() + pageable.getPageSize())); + Math.toIntExact(pageable.getPageSize())); int tot = cs.countCollectionsWithSubmit(query, context, null, entityTypeLabel); return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); } catch (SQLException e) { @@ -243,6 +244,33 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository findSubmitAuthorizedByCommunityAndEntityType( + @Parameter(value = "query") String query, + @Parameter(value = "uuid", required = true) UUID communityUuid, + @Parameter(value = "entityType", required = true) String entityTypeLabel, + Pageable pageable) { + try { + Context context = obtainContext(); + EntityType entityType = entityTypeService.findByEntityType(context, entityTypeLabel); + if (Objects.isNull(entityType)) { + throw new ResourceNotFoundException("There was no entityType found with label: " + entityTypeLabel); + } + Community community = communityService.find(context, communityUuid); + if (Objects.isNull(community)) { + throw new ResourceNotFoundException( + CommunityRest.CATEGORY + "." + CommunityRest.NAME + " with id: " + communityUuid + " not found"); + } + List collections = cs.findCollectionsWithSubmit(query, context, community, entityTypeLabel, + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getPageSize())); + int total = cs.countCollectionsWithSubmit(query, context, community, entityTypeLabel); + return converter.toRestPage(collections, pageable, total, utils.obtainProjection()); + } catch (SQLException | SearchServiceException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override @PreAuthorize("hasPermission(#id, 'COLLECTION', 'WRITE')") protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 2d19b0a4bb..6d681dbf07 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -3363,4 +3363,184 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes ))); } + @Test + public void findSubmitAuthorizedCollectionsByCommunityAndEntityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community Two") + .build(); + + CollectionBuilder.createCollection(context, child1) + .withEntityType(publication.getLabel()) + .withName("Test Collection 1") + .withSubmitterGroup(eperson) + .build(); + + Collection col2 = CollectionBuilder.createCollection(context, child2) + .withEntityType(publication.getLabel()) + .withName("Publication Collection 2") + .withSubmitterGroup(eperson) + .build(); + + Collection col3 = CollectionBuilder.createCollection(context, child2) + .withEntityType(publication.getLabel()) + .withName("Publication Collection 3") + .withSubmitterGroup(eperson) + .build(); + + Collection colWithoutEntity = CollectionBuilder.createCollection(context, child2) + .withName(" Test Collection 4") + .withSubmitterGroup(eperson) + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunityAndEntityType") + .param("uuid", child2.getID().toString()) + .param("entityType", publication.getLabel())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", equalTo(2))) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchCollection(col2), + CollectionMatcher.matchCollection(col3)))) + .andExpect(jsonPath("$._embedded.collections", not(containsInAnyOrder( + CollectionMatcher.matchCollection(colWithoutEntity))))); + } + + @Test + public void findSubmitAuthorizedCollectionsByCommunityAndEntityWithQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType journal = EntityTypeBuilder.createEntityTypeBuilder(context, "Journal").build(); + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType(journal.getLabel()) + .withName("Test Collection 1") + .withSubmitterGroup(eperson) + .build(); + + Collection col2 = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType(journal.getLabel()) + .withName("Publication Collection 2") + .withSubmitterGroup(eperson) + .build(); + + Collection col3 = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType(publication.getLabel()) + .withName("Publication Collection 3 Test") + .withSubmitterGroup(eperson) + .build(); + + Collection colWithoutEntity = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 4 Test") + .withSubmitterGroup(eperson) + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunityAndEntityType") + .param("uuid", parentCommunity.getID().toString()) + .param("entityType", journal.getLabel()) + .param("query", "test")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", equalTo(1))) + .andExpect(jsonPath("$._embedded.collections", contains(CollectionMatcher.matchCollection(col1)))) + .andExpect(jsonPath("$._embedded.collections",not(contains( + CollectionMatcher.matchCollection(colWithoutEntity))))); + + getClient(token).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunityAndEntityType") + .param("uuid", parentCommunity.getID().toString()) + .param("entityType", publication.getLabel()) + .param("query", "publication")) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", equalTo(1))) + .andExpect(jsonPath("$._embedded.collections", contains(CollectionMatcher.matchCollection(col3)))) + .andExpect(jsonPath("$._embedded.collections", not(containsInAnyOrder( + CollectionMatcher.matchCollection(colWithoutEntity), + CollectionMatcher.matchCollection(col2))))); + } + + @Test + public void findSubmitAuthorizedAllCollectionsByCommunityAndEntityBadRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType(publication.getLabel()) + .withName("Test Collection 1") + .withSubmitterGroup(eperson) + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunityAndEntityType")) + .andExpect(status().isBadRequest()); + + // missing entityType param + getClient(token).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunityAndEntityType") + .param("uuid", parentCommunity.getID().toString())) + .andExpect(status().isBadRequest()); + + // missing community uuid param + getClient(token).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunityAndEntityType") + .param("entityType", publication.getLabel())) + .andExpect(status().isBadRequest()); + } + + @Test + public void findSubmitAuthorizedByCommunityAndEntityTypeNotFoundTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType(publication.getLabel()) + .withName("Test Collection 1") + .withSubmitterGroup(eperson) + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunityAndEntityType") + .param("entityType", publication.getLabel()) + .param("uuid", UUID.randomUUID().toString())) + .andExpect(status().isNotFound()); + + getClient(token).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunityAndEntityType") + .param("entityType", "test") + .param("uuid", parentCommunity.getID().toString())) + .andExpect(status().isNotFound()); + } + } diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index 8df40c2728..09eeb173e2 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -265,7 +265,7 @@ - + @@ -345,5 +345,6 @@ or to add multiple fields to the same field for easier/faster searching. --> + From ded4f334ece740a80ad15015346def90972e4861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 21 Sep 2021 16:37:59 +0100 Subject: [PATCH 0296/1254] adding integration tests --- .../external/OpenAIRERestConnector.java | 13 +- .../impl/OpenAIREFundingDataProvider.java | 26 +- .../config/spring/api/external-openaire.xml | 27 + .../OpenAIREFundingExternalSourcesIT.java | 95 +++ .../impl/MockOpenAIREFundingDataProvider.java | 75 +++ .../provider/impl/openaire-no-projects.xml | 16 + .../provider/impl/openaire-project.xml | 74 +++ .../provider/impl/openaire-projects.xml | 610 ++++++++++++++++++ 8 files changed, 918 insertions(+), 18 deletions(-) create mode 100644 dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenAIREFundingDataProvider.java create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-no-projects.xml create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-project.xml create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-projects.xml 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 f748894fe9..8f36141a2d 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -53,22 +53,26 @@ public class OpenAIRERestConnector { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIRERestConnector.class); /** - * OpenAIRE API Url + * OpenAIRE API Url + * and can be configured with: openaire.api.url */ - private String url; + private String url = "https://api.openaire.eu"; /** * Boolean with token usage definition true if we want to use a token + * and can be configured with: openaire.token.enabled */ - boolean tokenEnabled; + boolean tokenEnabled = false; /** * OpenAIRE Authorization and Authentication Token Service URL + * and can be configured with: openaire.token.url */ private String tokenServiceUrl; /** * OpenAIRE clientId + * and can be configured with: openaire.token.clientId */ private String clientId; @@ -79,13 +83,16 @@ public class OpenAIRERestConnector { /** * OpenAIRE clientSecret + * and can be configured with: openaire.token.clientSecret */ private String clientSecret; + public OpenAIRERestConnector(String url) { this.url = url; } + /** * This method grabs an accessToken an sets the expiration time Based.
    * Based on https://develop.openaire.eu/basic.html diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java index e424e08966..429275cea0 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java @@ -18,6 +18,13 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.external.OpenAIRERestConnector; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.ExternalDataProvider; +import org.springframework.beans.factory.annotation.Autowired; import eu.openaire.jaxb.helper.FundingHelper; import eu.openaire.jaxb.helper.ProjectHelper; import eu.openaire.jaxb.model.Response; @@ -26,13 +33,6 @@ import eu.openaire.oaf.model.base.FunderType; import eu.openaire.oaf.model.base.FundingTreeType; import eu.openaire.oaf.model.base.FundingType; import eu.openaire.oaf.model.base.Project; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.content.dto.MetadataValueDTO; -import org.dspace.external.OpenAIRERestConnector; -import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; -import org.springframework.beans.factory.annotation.Autowired; /** * This class is the implementation of the ExternalDataProvider interface that @@ -81,7 +81,7 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { @Override public Optional getExternalDataObject(String id) { - + // we use base64 encoding in order to use slashes / and other // characters that must be escaped for the <:entry-id> String decodedId = new String(Base64.getDecoder().decode(id)); @@ -90,10 +90,8 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { try { if (response.getHeader() != null && Integer.parseInt(response.getHeader().getTotal()) > 0) { Project project = response.getResults().getResult().get(0).getMetadata().getEntity().getProject(); - ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider.ExternalDataObjectBuilder(project) - .setId(generateProjectURI(project)) - .setSource(sourceIdentifier) - .build(); + ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider.ExternalDataObjectBuilder( + project).setId(generateProjectURI(project)).setSource(sourceIdentifier).build(); return Optional.of(externalDataObject); } } catch (NumberFormatException e) { @@ -137,9 +135,7 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { if (projects.size() > 0) { return projects.stream() .map(project -> new OpenAIREFundingDataProvider.ExternalDataObjectBuilder(project) - .setId(generateProjectURI(project)) - .setSource(sourceIdentifier) - .build()) + .setId(generateProjectURI(project)).setSource(sourceIdentifier).build()) .collect(Collectors.toList()); } return Collections.emptyList(); diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml new file mode 100644 index 0000000000..7698b50f8e --- /dev/null +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java new file mode 100644 index 0000000000..d734f08c7a --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java @@ -0,0 +1,95 @@ +package org.dspace.app.rest; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.ExternalSourceEntryMatcher; +import org.dspace.app.rest.matcher.ExternalSourceMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class OpenAIREFundingExternalSourcesIT extends AbstractControllerIntegrationTest { + + /** + * Test openaire funding external source + * + * @throws Exception + */ + @Test + public void findOneOpenAIREFundingExternalSourceTest() throws Exception { + getClient().perform(get("/api/integration/externalsources")).andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItem( + ExternalSourceMatcher.matchExternalSource("openAIREFunding", "openAIREFunding", false)))); + } + + /** + * Test openaire funding entries for a query returning no results + * + * @throws Exception + */ + @Test + public void findOneOpenAIREFundingExternalSourceEntriesEmptyWithQueryTest() throws Exception { + + getClient().perform(get("/api/integration/externalsources/openAIREFunding/entries").param("query", "empty")) + .andExpect(status().isOk()).andExpect(jsonPath("$.page.number", is(0))); + } + + /** + * Test openaire funding entries with multiple keywords for a query returning no + * results + * + * @throws Exception + */ + @Test + public void findOneOpenAIREFundingExternalSourceEntriesWithQueryMultipleKeywordsTest() throws Exception { + + getClient() + .perform( + get("/api/integration/externalsources/openAIREFunding/entries").param("query", "empty+results")) + .andExpect(status().isOk()).andExpect(jsonPath("$.page.number", is(0))); + } + + /** + * Test openaire funding entries for a query using ?query=mock + * + * @throws Exception + */ + @Test + public void findOneOpenAIREFundingExternalSourceEntriesWithQueryTest() throws Exception { + getClient().perform(get("/api/integration/externalsources/openAIREFunding/entries").param("query", "mushroom")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.externalSourceEntries", + Matchers.hasItem(ExternalSourceEntryMatcher.matchExternalSourceEntry( + "aW5mbzpldS1yZXBvL2dyYW50QWdyZWVtZW50L05XTy8rLzIzMDAxNDc3MjgvTkw=", + "Master switches of initiation of mushroom formation", + "Master switches of initiation of mushroom formation", "openAIREFunding")))); + + } + + /** + * Test openaire funding entry value + * + * @throws Exception + */ + @Test + public void findOneOpenAIREFundingExternalSourceEntryValueTest() throws Exception { + + // "info:eu-repo/grantAgreement/mock/mock/mock/mock" base64 encoded + String projectID = "aW5mbzpldS1yZXBvL2dyYW50QWdyZWVtZW50L0ZDVC81ODc2LVBQQ0RUSS8xMTAwNjIvUFQ="; + String projectName = "Portuguese Wild Mushrooms: Chemical characterization and functional study" + + " of antiproliferative and proapoptotic properties in cancer cell lines"; + + getClient().perform(get("/api/integration/externalsources/openAIREFunding/entryValues/" + projectID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.allOf(hasJsonPath("$.id", is(projectID)), hasJsonPath("$.display", is(projectName)), + hasJsonPath("$.value", is(projectName)), + hasJsonPath("$.externalSource", is("openAIREFunding")), + hasJsonPath("$.type", is("externalSourceEntry"))))); + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenAIREFundingDataProvider.java b/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenAIREFundingDataProvider.java new file mode 100644 index 0000000000..baf1041ab5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockOpenAIREFundingDataProvider.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.external.provider.impl; + +import static org.mockito.Mockito.when; + +import java.io.IOException; +import javax.xml.bind.JAXBException; + +import eu.openaire.jaxb.helper.OpenAIREHandler; +import eu.openaire.jaxb.model.Response; +import org.dspace.external.OpenAIRERestConnector; +import org.mockito.AdditionalMatchers; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** + * Mock the OpenAIRE external source using a mock rest connector so that query + * will be resolved against static test files + * + */ +public class MockOpenAIREFundingDataProvider extends OpenAIREFundingDataProvider { + @Override + public void init() throws IOException { + OpenAIRERestConnector restConnector = Mockito.mock(OpenAIRERestConnector.class); + + when(restConnector.searchProjectByKeywords(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt(), + ArgumentMatchers.startsWith("mushroom"))).thenAnswer(new Answer() { + public Response answer(InvocationOnMock invocation) { + try { + return OpenAIREHandler + .unmarshal(this.getClass().getResourceAsStream("openaire-projects.xml")); + } catch (JAXBException e) { + e.printStackTrace(); + } + return null; + } + }); + + when(restConnector.searchProjectByKeywords(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt(), + AdditionalMatchers.not(ArgumentMatchers.startsWith("mushroom")))).thenAnswer(new Answer() { + public Response answer(InvocationOnMock invocation) { + try { + return OpenAIREHandler + .unmarshal(this.getClass().getResourceAsStream("openaire-no-projects.xml")); + } catch (JAXBException e) { + e.printStackTrace(); + } + return null; + } + }); + + when(restConnector.searchProjectByIDAndFunder(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), + ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())).thenAnswer(new Answer() { + public Response answer(InvocationOnMock invocation) { + try { + return OpenAIREHandler + .unmarshal(this.getClass().getResourceAsStream("openaire-project.xml")); + } catch (JAXBException e) { + e.printStackTrace(); + } + return null; + } + }); + + setConnector(restConnector); + } +} diff --git a/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-no-projects.xml b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-no-projects.xml new file mode 100644 index 0000000000..481f2e14ec --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-no-projects.xml @@ -0,0 +1,16 @@ + + +

    + (oaftype exact project) and ( mushroomss) + en_US + 10 + 1 + 0 + + +
    + + + + + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-project.xml b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-project.xml new file mode 100644 index 0000000000..2b7c489442 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-project.xml @@ -0,0 +1,74 @@ + + +
    + (oaftype exact project) and (projectcode_nt exact "110062") and (fundershortname exact "FCT") + en_US + 10 + 1 + 1 + + +
    + + +
    + fct_________::59523e9f4736c2cab70a470f088b53dd + 2021-08-06 + 2021-08-06 +
    + + + + + fct_________::110062 + http://www.fct.pt/apoios/projectos/consulta/vglobal_projecto.phtml.en?idProjecto=110062&idElemConcurso=3734 + 110062 + PTDC/AGR-ALI/110062/2009 + Portuguese Wild Mushrooms: Chemical characterization and functional study of antiproliferative and proapoptotic properties in cancer cell lines + 2010-12-24 + 2013-12-23 + PTDC/2009 + Agricultural and Forestry Sciences - Food Science and Technology + 0 + false + false + false + 0.0 + 0.0 + + + fct_________::FCT + FCT + Fundao para a Cincia e a Tecnologia, I.P. + PT + + + fct_________::FCT::5876-PPCDTI + 5876-PPCDTI + 5876-PPCDTI + + fct:program + + + + false + false + 0.900 + null + + + + + + + +
    +
    + + +
    \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-projects.xml b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-projects.xml new file mode 100644 index 0000000000..225055eb92 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-projects.xml @@ -0,0 +1,610 @@ + + +
    + (oaftype exact project) and ( mushroom) + en_US + 10 + 1 + 77 + + +
    + + +
    + rcuk________::98b50e6e0715fad40627833c7030d3c3 + 2018-02-06 + 2021-02-20 +
    + + + + + rcuk________::103679 + 103679 + Mushroom Robo-Pic - Development of an autonomous robotic mushroom picking system + 2017-10-01 + 2019-06-30 + 0 + false + false + false + 0.0 + 0.0 + + + rcuk________::RCUK + UKRI + UK Research and Innovation + GB + + + rcuk________::RCUK::Innovate UK + Innovate UK + Innovate UK + + rcuk:fundingStream + + + + false + false + 0.900 + null + + + + + rcuk________::ebafcf5f45afa2e9807f981e668db66b + Littleport Mushroom Farms Llp + + + + + + + +
    + +
    + rcuk________::6ac77c83ee0d98c433f91f3dc83074b2 + 2017-11-04 + 2021-02-20 +
    + + + + + rcuk________::752540 + 752540 + Exending Shelf Life of Mushroom Growing Kits + 2015-05-01 + 2015-10-31 + 0 + false + false + false + 0.0 + 0.0 + + + rcuk________::RCUK + UKRI + UK Research and Innovation + GB + + + rcuk________::RCUK::Innovate UK + Innovate UK + Innovate UK + + rcuk:fundingStream + + + + false + false + 0.900 + null + + + + + rcuk________::f2a533e22408279c50f647779633cf69 + Espresso Mushroom Company Ltd + + + + + + + +
    + +
    + arc_________::e1da9b244237847b24379fb1b11fb151 + 2015-08-24 + 2018-11-20 +
    + + + + arc_________::LP0220040 + http://purl.org/au-research/grants/arc/LP0220040 + LP0220040 + Use of Organic Residues in Edible Mushroom Production + 2002-01-01 + 2003-12-31 + compost,exotic mushrooms,mushroom production,organic wastes,peat,wood processing wastes + 0 + false + false + false + 0.0 + 0.0 + + + arc_________::ARC + ARC + Australian Research Council (ARC) + AU + + + arc_________::ARC::Linkage Projects + Linkage Projects + Linkage Projects + + arc:fundingStream + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + rcuk________::c137d9bfad46b1ebcc9b3f06e6eb5683 + 2018-08-01 + 2021-02-20 +
    + + + + + rcuk________::133611 + 133611 + The development of a mushroom harvesting machine to increase yield and production while reducing waste and labour shortage risk + 2018-07-01 + 2019-06-30 + 0 + false + false + false + 0.0 + 0.0 + + + rcuk________::RCUK + UKRI + UK Research and Innovation + GB + + + rcuk________::RCUK::Innovate UK + Innovate UK + Innovate UK + + rcuk:fundingStream + + + + false + false + 0.900 + null + + + + + rcuk________::560dfc3b58d1ab0d8a8957a943a76962 + + Mushroom Machine Company Limited + + + + + + +
    + +
    + corda__h2020::c97f7d6f1ff338991c0ec20b33ddb1e0 + 2018-07-21 + 2021-07-19 +
    + + + + + corda__h2020::820352 + 820352 + Smartmushroom + Smart MAnagement of spent mushRoom subsTrate to lead the MUSHROOM sector towards a circular economy + 2018-08-01 + 2021-01-31 + H2020-EIC-FTI-2018-2020 + 0 + false + Fast Track to Innovation (FTI) + true + false + Waste from animal breeding and agriculture, specifically horse and chicken manure and wheat straw, are the raw materials of the growing substrate of mushroom. To grow 1 tonne of mushroom, 3 to 4 tonnes of substrate are needed. However, when mushroom production is completed the substrate cannot be used for another growing cycle due to the depletion of nutrients needed for mushroom growing and it is called Spent Mushroom Substrate (SMS) and becomes a waste that should be managed according to regulations. In Europe, c.a. 3.65 million tons of SMS are generated each year. SMS is a high-moisture content bulk material rich in organic matter and nutrients and it could be reused in agriculture by adding it to the soils as amendment or mulch or weathered to be reused as casing soil. However, nitrates directive set a disposal limit that makes that large quantities of SMS cannot be simply spread in soils next to growers’ facilities, as there is a high risk of leachates and water pollution. Due to its low bulk density and high water content, transportation costs are high and therefore storage is becoming a sound problem. SmartMUSHROOM aims to increase mushroom growers’ waste management efficiency by using a new technology which allow them to obtain enough biogas from fresh SMS to dry a mixture of digestate and additional fresh SMS and pelletize it targeting to obtain a marketable high-quality organic fertilizer rich in organic matter and in nutrients, easy to handle, store and transport to any farming region in Europe. A perfect example of biobased circular economy. The aim of the project is to build a pilot plant to demonstrate the technology and find the best commercial formulation for the pellets to enter organic farming market. After the end of project we aim to build at least 18 treatment plants that will place in market 153,000 tonnes of SMS-pellets, generating a total Turnover of 54M€ in the period 2021-2025 and up to 105 related new jobs. + EUR + 2977940.0 + 2264140.0 + + + ec__________::EC + EC + European Commission + EU + + + ec__________::EC::H2020::IA + Innovation action + IA + ec:h2020toas + + + ec__________::EC::H2020 + H2020 + Horizon 2020 Framework Programme + + ec:h2020fundings + + + + + + false + false + 0.900 + null + + + + + pending_org_::39d5641e14f7cfe56e3e836199e33eba + ECOBELIEVE DOO + + ECOBELIEVE DOO GRKINJA + + + pending_org_::3c128a988f1404fed53e1cd8a61df51b + Asociacion Profesional de Productores de Compost y Hongos, de la Rioja, Navarra y Aragón + + ASOCHAMP + + + pending_org_::7555b3c59a033e789ce6190a6c9f39aa + INVESTIGACION Y DESARROLLO CASTILLA Y LEON S.A + + IDECAL S.A. + + + pending_org_::4193497542c7b30e3f50f52414905579 + NOVIS GMBH + + NOVIS GMBH + + + + + + +
    + +
    + nwo_________::c091653b5930a5a11c748720167b04d7 + 2016-06-23 + 2018-08-07 +
    + + + + + nwo_________::2300148817 + 2300148817 + Production of therapeutic proteins in mushroom + 2007-09-01 + 2012-04-30 + 0 + false + false + false + 0.0 + 0.0 + + + nwo_________::NWO + NWO + Netherlands Organisation for Scientific Research (NWO) + NL + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + nwo_________::5ee5a31d8c77215faef3bd35bd9696ff + 2016-06-23 + 2018-08-07 +
    + + + + + nwo_________::2300148209 + 2300148209 + Control of Verticillium fungicola on mushroom + 2006-10-01 + 2012-08-10 + 0 + false + false + false + 0.0 + 0.0 + + + nwo_________::NWO + NWO + Netherlands Organisation for Scientific Research (NWO) + NL + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + nwo_________::98df9c76cbd6537553284af850398659 + 2016-06-23 + 2018-08-07 +
    + + + + + nwo_________::2300147728 + 2300147728 + Master switches of initiation of mushroom formation + 2005-11-01 + 2012-09-12 + 0 + false + false + false + 0.0 + 0.0 + + + nwo_________::NWO + NWO + Netherlands Organisation for Scientific Research (NWO) + NL + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + nwo_________::7ddc7f2f259735f312a27c54f6b2ee5d + 2016-06-23 + 2018-08-07 +
    + + + + + nwo_________::2300164658 + 2300164658 + Push the white button; controlling mushroom formation + 2011-09-01 + 0 + false + false + false + 0.0 + 0.0 + + + nwo_________::NWO + NWO + Netherlands Organisation for Scientific Research (NWO) + NL + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + nsf_________::c5fa29db776dc4f21919e12cbaea37eb + 2016-03-11 + 2018-08-07 +
    + + + + + nsf_________::6112554 + 6112554 + Respiratory Mechanisms in Cultivated Mushroom + 1961-01-01 + 1963-01-01 + 0 + false + false + false + 0.0 + 0.0 + + + nsf_________::NSF + NSF + National Science Foundation + US + + + + false + false + 0.900 + null + + + + + openorgs____::2f4b2e4dcb319a5f66e887d2fd555734 + + University of Delaware + UD + + + + + + +
    + +
    + + +
    \ No newline at end of file From a3a1019db7ae9fbd397da9496260462f621a69ad Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 21 Sep 2021 18:56:39 +0200 Subject: [PATCH 0297/1254] implemented entityType and externalSources end points and tests --- .../dspace/content/EntityTypeServiceImpl.java | 58 +++++ .../content/service/EntityTypeService.java | 5 + .../AbstractExternalDataProvider.java | 43 ++++ .../provider/ExternalDataProvider.java | 12 + .../provider/impl/LiveImportDataProvider.java | 4 +- .../impl/OrcidV3AuthorDataProvider.java | 10 +- .../impl/SHERPAv2JournalDataProvider.java | 7 +- .../impl/SHERPAv2JournalISSNDataProvider.java | 4 +- .../impl/SHERPAv2PublisherDataProvider.java | 9 +- .../external/service/ExternalDataService.java | 21 ++ .../service/impl/ExternalDataServiceImpl.java | 13 +- .../config/spring/api/external-services.xml | 20 ++ .../provider/impl/MockDataProvider.java | 4 +- .../repository/EntityTypeRestRepository.java | 55 +++++ .../ExternalSourceRestRepository.java | 15 ++ .../app/rest/EntityTypeRestRepositoryIT.java | 233 +++++++++++++++++- .../rest/ExternalSourcesRestControllerIT.java | 57 +++++ .../provider/impl/MockDataProvider.java | 4 +- 18 files changed, 543 insertions(+), 31 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/external/provider/AbstractExternalDataProvider.java diff --git a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java index 4577054ff0..f3e156dd42 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java @@ -7,16 +7,30 @@ */ package org.dspace.content; +import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; import org.apache.commons.collections.CollectionUtils; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.response.FacetField; +import org.apache.solr.client.solrj.response.FacetField.Count; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.params.FacetParams; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.EntityTypeDAO; import org.dspace.content.service.EntityTypeService; import org.dspace.core.Context; +import org.dspace.discovery.SolrSearchCore; +import org.dspace.discovery.indexobject.IndexableCollection; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; import org.springframework.beans.factory.annotation.Autowired; public class EntityTypeServiceImpl implements EntityTypeService { @@ -27,6 +41,12 @@ public class EntityTypeServiceImpl implements EntityTypeService { @Autowired(required = true) protected AuthorizeService authorizeService; + @Autowired + protected GroupService groupService; + + @Autowired + protected SolrSearchCore solrSearchCore; + @Override public EntityType findByEntityType(Context context, String entityType) throws SQLException { return entityTypeDAO.findByEntityType(context, entityType); @@ -98,4 +118,42 @@ public class EntityTypeServiceImpl implements EntityTypeService { } entityTypeDAO.delete(context, entityType); } + + @Override + public List getSubmitAuthorizedTypes(Context context) + throws SQLException, SolrServerException, IOException { + List types = new ArrayList<>(); + StringBuilder query = new StringBuilder(); + org.dspace.eperson.EPerson currentUser = context.getCurrentUser(); + if (!authorizeService.isAdmin(context)) { + String userId = ""; + if (currentUser != null) { + userId = currentUser.getID().toString(); + } + query.append("submit:(e").append(userId); + Set groups = groupService.allMemberGroupsSet(context, currentUser); + for (Group group : groups) { + query.append(" OR g").append(group.getID()); + } + query.append(")"); + } else { + query.append("*:*"); + } + + SolrQuery sQuery = new SolrQuery(query.toString()); + sQuery.addFilterQuery("search.resourcetype:" + IndexableCollection.TYPE); + sQuery.setRows(0); + sQuery.addFacetField("search.entitytype"); + sQuery.setFacetMinCount(1); + sQuery.setFacetLimit(Integer.MAX_VALUE); + sQuery.setFacetSort(FacetParams.FACET_SORT_INDEX); + QueryResponse qResp = solrSearchCore.getSolr().query(sQuery); + FacetField facetField = qResp.getFacetField("search.entitytype"); + if (Objects.nonNull(facetField)) { + for (Count c : facetField.getValues()) { + types.add(c.getName()); + } + } + return types; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java index f4d9a15bb2..b06bc34c50 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java @@ -7,9 +7,11 @@ */ package org.dspace.content.service; +import java.io.IOException; import java.sql.SQLException; import java.util.List; +import org.apache.solr.client.solrj.SolrServerException; import org.dspace.authorize.AuthorizeException; import org.dspace.content.EntityType; import org.dspace.core.Context; @@ -56,4 +58,7 @@ public interface EntityTypeService extends DSpaceCRUDService { * @throws AuthorizeException If something geos wrong with authorizations */ public EntityType create(Context context, String entityTypeString) throws SQLException, AuthorizeException; + + public List getSubmitAuthorizedTypes(Context context) throws SQLException, SolrServerException, IOException; + } diff --git a/dspace-api/src/main/java/org/dspace/external/provider/AbstractExternalDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/AbstractExternalDataProvider.java new file mode 100644 index 0000000000..e08481e377 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/external/provider/AbstractExternalDataProvider.java @@ -0,0 +1,43 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.external.provider; +import java.util.List; +import java.util.Objects; + +/** + * This abstract class allows to configure the list of supported entity types + * via spring. If no entity types are explicitly configured it is assumed that + * the provider can be used with any entity type + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public abstract class AbstractExternalDataProvider implements ExternalDataProvider { + + private List supportedEntityTypes; + + public void setSupportedEntityTypes(List supportedEntityTypes) { + this.supportedEntityTypes = supportedEntityTypes; + } + + public List getSupportedEntityTypes() { + return supportedEntityTypes; + } + + /** + * Return true if the supportedEntityTypes list is empty or contains the requested entity type + * + * @param entityType the entity type to check + * @return true if the external provider can be used to search for items of the + * specified type + */ + @Override + public boolean supportsEntityType(String entityType) { + return Objects.isNull(supportedEntityTypes) || supportedEntityTypes.contains(entityType); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/external/provider/ExternalDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/ExternalDataProvider.java index 5c921efd35..1227b4b2ff 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/ExternalDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/ExternalDataProvider.java @@ -57,4 +57,16 @@ public interface ExternalDataProvider { */ public int getNumberOfResults(String query); + /** + * Override this method to limit the external data provider to specific entity + * types (Publication, OrgUnit, etc.) + * + * @param entityType the entity type to check + * @return true if the external provider can be used to search for items of the + * specified type + */ + public default boolean supportsEntityType(String entityType) { + return true; + } + } 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 45855a74ad..962183fa6f 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 @@ -15,7 +15,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.metadatamapping.MetadatumDTO; @@ -27,7 +27,7 @@ import org.dspace.importer.external.service.components.QuerySource; * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -public class LiveImportDataProvider implements ExternalDataProvider { +public class LiveImportDataProvider extends AbstractExternalDataProvider { /** * The {@link QuerySource} live import provider */ diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java index 629ff3829d..0653ee758d 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OrcidV3AuthorDataProvider.java @@ -16,6 +16,7 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -29,7 +30,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.OrcidRestConnector; import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.external.provider.orcid.xml.XMLtoBio; import org.json.JSONObject; import org.orcid.jaxb.model.v3.release.common.OrcidIdentifier; @@ -41,7 +42,7 @@ import org.springframework.beans.factory.annotation.Autowired; * This class is the implementation of the ExternalDataProvider interface that will deal with the OrcidV3 External * Data lookup */ -public class OrcidV3AuthorDataProvider implements ExternalDataProvider { +public class OrcidV3AuthorDataProvider extends AbstractExternalDataProvider { private static final Logger log = LogManager.getLogger(OrcidV3AuthorDataProvider.class); @@ -217,11 +218,10 @@ public class OrcidV3AuthorDataProvider implements ExternalDataProvider { } catch (IOException e) { log.error(e.getMessage(), e); } - if (bios == null) { + if (Objects.isNull(bios)) { return Collections.emptyList(); - } else { - return bios.stream().map(bio -> convertToExternalDataObject(bio)).collect(Collectors.toList()); } + return bios.stream().map(bio -> convertToExternalDataObject(bio)).collect(Collectors.toList()); } @Override diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java index 42d3cab494..a4276c83ed 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalDataProvider.java @@ -15,14 +15,13 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; import org.dspace.app.sherpa.SHERPAService; import org.dspace.app.sherpa.v2.SHERPAJournal; import org.dspace.app.sherpa.v2.SHERPAResponse; import org.dspace.app.sherpa.v2.SHERPAUtils; import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.AbstractExternalDataProvider; /** * This class is the implementation of the ExternalDataProvider interface that will deal with SherpaJournal External @@ -31,9 +30,7 @@ import org.dspace.external.provider.ExternalDataProvider; * * @author Kim Shepherd */ -public class SHERPAv2JournalDataProvider implements ExternalDataProvider { - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SHERPAv2JournalDataProvider.class); +public class SHERPAv2JournalDataProvider extends AbstractExternalDataProvider { // Source identifier (configured in spring configuration) private String sourceIdentifier; diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java index ed2df53c83..9e61b9ac2a 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2JournalISSNDataProvider.java @@ -22,7 +22,7 @@ import org.dspace.app.sherpa.v2.SHERPAResponse; import org.dspace.app.sherpa.v2.SHERPAUtils; import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.AbstractExternalDataProvider; /** * This class is the implementation of the ExternalDataProvider interface that will deal with SherpaJournal External @@ -32,7 +32,7 @@ import org.dspace.external.provider.ExternalDataProvider; * * @author Kim Shepherd */ -public class SHERPAv2JournalISSNDataProvider implements ExternalDataProvider { +public class SHERPAv2JournalISSNDataProvider extends AbstractExternalDataProvider { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger( diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2PublisherDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2PublisherDataProvider.java index 03f172853e..af922220ce 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2PublisherDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/SHERPAv2PublisherDataProvider.java @@ -15,14 +15,13 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; import org.dspace.app.sherpa.SHERPAService; import org.dspace.app.sherpa.v2.SHERPAPublisher; import org.dspace.app.sherpa.v2.SHERPAPublisherResponse; import org.dspace.app.sherpa.v2.SHERPAUtils; import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.AbstractExternalDataProvider; /** * This class is the implementation of the ExternalDataProvider interface that will deal with SHERPAPublisher External @@ -33,11 +32,7 @@ import org.dspace.external.provider.ExternalDataProvider; * * @author Kim Shepherd */ -public class SHERPAv2PublisherDataProvider implements ExternalDataProvider { - - // Logger - private static final Logger log = - org.apache.logging.log4j.LogManager.getLogger(SHERPAv2PublisherDataProvider.class); +public class SHERPAv2PublisherDataProvider extends AbstractExternalDataProvider { // Source identifier (eg 'sherpaPublisher') configured in spring configuration private String sourceIdentifier; diff --git a/dspace-api/src/main/java/org/dspace/external/service/ExternalDataService.java b/dspace-api/src/main/java/org/dspace/external/service/ExternalDataService.java index e0c241ba4a..53423395e3 100644 --- a/dspace-api/src/main/java/org/dspace/external/service/ExternalDataService.java +++ b/dspace-api/src/main/java/org/dspace/external/service/ExternalDataService.java @@ -77,4 +77,25 @@ public interface ExternalDataService { WorkspaceItem createWorkspaceItemFromExternalDataObject(Context context, ExternalDataObject externalDataObject, Collection collection) throws AuthorizeException, SQLException; + + /** + * Return the ExternalDataProvider that supports a specific entity type + * + * @param entityType + * @return list of ExternalDataProviders that supports a specific entity type + */ + public List getExternalDataProvidersForEntityType(String entityType); + + /** + * Override this method to limit the external data provider to specific entity + * types (Publication, OrgUnit, etc.) + * + * @param entityType the entity type to check + * @return true if the external provider can be used to search for items of the + * specified type + */ + public default boolean supportsEntityType(String entityType) { + return true; + } + } diff --git a/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java b/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java index 227a31e6fb..f91ea00cac 100644 --- a/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/external/service/impl/ExternalDataServiceImpl.java @@ -10,10 +10,10 @@ package org.dspace.external.service.impl; import java.sql.SQLException; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; 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.Item; import org.dspace.content.WorkspaceItem; @@ -44,9 +44,6 @@ public class ExternalDataServiceImpl implements ExternalDataService { @Autowired private WorkspaceItemService workspaceItemService; - @Autowired - private AuthorizeService authorizeService; - @Override public Optional getExternalDataObject(String source, String id) { ExternalDataProvider provider = getExternalDataProvider(source); @@ -110,4 +107,12 @@ public class ExternalDataServiceImpl implements ExternalDataService { externalDataObject.getId())); return workspaceItem; } + + @Override + public List getExternalDataProvidersForEntityType(String entityType) { + return externalDataProviders.stream() + .filter(edp -> edp.supportsEntityType(entityType)) + .collect(Collectors.toList()); + } + } 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 590fd57cb6..cf36e7fd25 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 @@ -20,6 +20,11 @@
    + + + Journal + + @@ -30,6 +35,11 @@ + + + Journal + + @@ -40,6 +50,11 @@ + + + OrgUnit + + @@ -48,6 +63,11 @@ + + + Publication + + diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java index e5a86f1f56..cdfbf1e943 100644 --- a/dspace-api/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java @@ -17,9 +17,9 @@ import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.AbstractExternalDataProvider; -public class MockDataProvider implements ExternalDataProvider { +public class MockDataProvider extends AbstractExternalDataProvider { private Map mockLookupMap; private String sourceIdentifier; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java index 29db39d879..341a8111e3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java @@ -7,13 +7,20 @@ */ package org.dspace.app.rest.repository; +import java.io.IOException; import java.sql.SQLException; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; +import org.apache.solr.client.solrj.SolrServerException; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.EntityTypeRest; import org.dspace.content.EntityType; import org.dspace.content.service.EntityTypeService; import org.dspace.core.Context; +import org.dspace.external.service.ExternalDataService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -29,6 +36,8 @@ public class EntityTypeRestRepository extends DSpaceRestRepository findAllByAuthorizedCollection(Pageable pageable) { + try { + Context context = obtainContext(); + List types = entityTypeService.getSubmitAuthorizedTypes(context); + List entityTypes = types.stream().map(type -> { + if (StringUtils.isBlank(type)) { + return null; + } + try { + return entityTypeService.findByEntityType(context, type); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + }).filter(x -> Objects.nonNull(x)).collect(Collectors.toList()); + return converter.toRestPage(entityTypes, pageable, utils.obtainProjection()); + } catch (SQLException | SolrServerException | IOException e) { + throw new RuntimeException(e); + } + } + + @SearchRestMethod(name = "findAllByAuthorizedExternalSource") + public Page findAllByAuthorizedExternalSource(Pageable pageable) { + try { + Context context = obtainContext(); + List types = entityTypeService.getSubmitAuthorizedTypes(context); + List entityTypes = types.stream() + .filter(x -> externalDataService.getExternalDataProvidersForEntityType(x).size() > 0) + .map(type -> { + if (StringUtils.isBlank(type)) { + return null; + } + try { + return entityTypeService.findByEntityType(context, type); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + }) + .filter(x -> Objects.nonNull(x)) + .collect(Collectors.toList()); + return converter.toRestPage(entityTypes, pageable, utils.obtainProjection()); + } catch (SQLException | SolrServerException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override public Class getDomainClass() { return EntityTypeRest.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java index 4a24d4e10e..474264520b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java @@ -9,7 +9,10 @@ package org.dspace.app.rest.repository; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.model.ExternalSourceEntryRest; import org.dspace.app.rest.model.ExternalSourceRest; @@ -96,6 +99,18 @@ public class ExternalSourceRestRepository extends DSpaceRestRepository findByEntityType(Context context, Pageable pageable, + @Parameter(required = true, value = "entityType") String entityType) { + List externalSources = externalDataService.getExternalDataProviders() + .stream() + .filter(ep -> ep.supportsEntityType(entityType)) + .collect(Collectors.toList()); + + return converter.toRestPage(externalSources, pageable, utils.obtainProjection()); + } + public Class getDomainClass() { return ExternalSourceRest.class; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 0f6060c011..5ab815ef6d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -6,7 +6,7 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest; - +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -14,17 +14,31 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Arrays; + import org.dspace.app.rest.matcher.EntityTypeMatcher; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.service.EntityTypeService; +import org.dspace.external.provider.AbstractExternalDataProvider; +import org.dspace.external.service.ExternalDataService; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +/** + * Integration test class for the entity type endpoint + * + * @author Mykhaylo Boychuk - 4Science + */ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { - + @Autowired + private ExternalDataService externalDataService; @Autowired private EntityTypeService entityTypeService; @@ -168,4 +182,219 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$.page.number", is(1))); } + @Test + public void findAllByAuthorizedCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("JournalIssue") + .withSubmitterGroup(eperson) + .withName("Collection 1") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .withSubmitterGroup(eperson) + .withName("Collection 2") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Project") + .withSubmitterGroup(eperson) + .withName("Collection 3") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Journal") + .withSubmitterGroup(eperson) + .withName("Collection 4") + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/entitytypes/search/findAllByAuthorizedCollection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalIssue")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")) + ))); + } + + @Test + public void findAllByAuthorizedCollectionPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("JournalIssue") + .withSubmitterGroup(eperson) + .withName("Collection 1") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .withSubmitterGroup(eperson) + .withName("Collection 2") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Project") + .withSubmitterGroup(eperson) + .withName("Collection 3") + .build(); + + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Journal") + .withSubmitterGroup(eperson) + .withName("Collection 4") + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/entitytypes/search/findAllByAuthorizedCollection") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalIssue")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")) + ))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("api/core/entitytypes/search/findAllByAuthorizedCollection?"), + Matchers.containsString("page=0"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("api/core/entitytypes/search/findAllByAuthorizedCollection?"), + Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("api/core/entitytypes/search/findAllByAuthorizedCollection?"), + Matchers.containsString("page=1"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("api/core/entitytypes/search/findAllByAuthorizedCollection?"), + Matchers.containsString("page=1"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient(token).perform(get("/api/core/entitytypes/search/findAllByAuthorizedCollection") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")) + ))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("api/core/entitytypes/search/findAllByAuthorizedCollection?"), + Matchers.containsString("page=0"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("api/core/entitytypes/search/findAllByAuthorizedCollection?"), + Matchers.containsString("page=1"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("api/core/entitytypes/search/findAllByAuthorizedCollection?"), + Matchers.containsString("page=0"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("api/core/entitytypes/search/findAllByAuthorizedCollection?"), + Matchers.containsString("page=1"), Matchers.containsString("size=2")))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.number", is(1))); + } + + @Test + public void findAllByAuthorizedExternalSource() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType publication = entityTypeService.findByEntityType(context, "Publication"); + EntityType orgUnit = entityTypeService.findByEntityType(context, "OrgUnit"); + EntityType project = entityTypeService.findByEntityType(context, "Project"); + EntityType funding = EntityTypeBuilder.createEntityTypeBuilder(context, "Funding").build(); + + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + CollectionBuilder.createCollection(context, rootCommunity) + .withEntityType(orgUnit.getLabel()) + .withName("Collection 1") + .build(); + + CollectionBuilder.createCollection(context, rootCommunity) + .withEntityType(publication.getLabel()) + .withSubmitterGroup(eperson) + .withName("Collection 2") + .build(); + + CollectionBuilder.createCollection(context, rootCommunity) + .withEntityType(project.getLabel()) + .withSubmitterGroup(eperson) + .withName("Collection 3") + .build(); + + CollectionBuilder.createCollection(context, rootCommunity) + .withEntityType(funding.getLabel()) + .withSubmitterGroup(eperson) + .withName("Collection 4") + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/core/entitytypes/search/findAllByAuthorizedExternalSource")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(publication), + EntityTypeMatcher.matchEntityTypeEntry(funding), + EntityTypeMatcher.matchEntityTypeEntry(project)))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/entitytypes/search/findAllByAuthorizedExternalSource")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(orgUnit), + EntityTypeMatcher.matchEntityTypeEntry(funding), + EntityTypeMatcher.matchEntityTypeEntry(project), + EntityTypeMatcher.matchEntityTypeEntry(publication)))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + + try { + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("mock")) + .setSupportedEntityTypes(Arrays.asList("Publication")); + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("pubmed")) + .setSupportedEntityTypes(Arrays.asList("Publication")); + + getClient(token).perform(get("/api/core/entitytypes/search/findAllByAuthorizedExternalSource")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entitytypes", contains( + EntityTypeMatcher.matchEntityTypeEntry(publication)))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + + getClient(adminToken).perform(get("/api/core/entitytypes/search/findAllByAuthorizedExternalSource")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(orgUnit), + EntityTypeMatcher.matchEntityTypeEntry(publication)))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))); + + } finally { + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("mock")) + .setSupportedEntityTypes(null); + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("pubmed")) + .setSupportedEntityTypes(null); + } + + } + } \ No newline at end of file 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 3497942be3..484171fcb6 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 @@ -125,4 +125,61 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati getClient().perform(get("/api/integration/externalsources/mock/entries")) .andExpect(status().isBadRequest()); } + + @Test + public void findExternalSourcesByEntityTypeTest() throws Exception { + getClient().perform(get("/api/integration/externalsources/search/findByEntityType") + .param("entityType", "Publication")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( + ExternalSourceMatcher.matchExternalSource("mock", "mock", false), + ExternalSourceMatcher.matchExternalSource("orcid", "orcid", false), + ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); + + getClient().perform(get("/api/integration/externalsources/search/findByEntityType") + .param("entityType", "Journal")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( + ExternalSourceMatcher.matchExternalSource("mock", "mock", false), + ExternalSourceMatcher.matchExternalSource("sherpaJournalIssn", "sherpaJournalIssn",false), + ExternalSourceMatcher.matchExternalSource("sherpaJournal", "sherpaJournal", false), + ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + } + + @Test + public void findExternalSourcesByEntityTypeBadRequestTest() throws Exception { + getClient().perform(get("/api/integration/externalsources/search/findByEntityType")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findExternalSourcesByEntityTypePaginationTest() throws Exception { + getClient().perform(get("/api/integration/externalsources/search/findByEntityType") + .param("entityType", "Journal") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( + ExternalSourceMatcher.matchExternalSource("mock", "mock", false), + ExternalSourceMatcher.matchExternalSource("sherpaJournalIssn", "sherpaJournalIssn",false) + ))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(2))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + + getClient().perform(get("/api/integration/externalsources/search/findByEntityType") + .param("entityType", "Journal") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( + ExternalSourceMatcher.matchExternalSource("sherpaJournal", "sherpaJournal", false), + ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false) + ))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(2))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java b/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java index b8dc7b501b..894b8e409a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java +++ b/dspace-server-webapp/src/test/java/org/dspace/external/provider/impl/MockDataProvider.java @@ -17,9 +17,9 @@ import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.AbstractExternalDataProvider; -public class MockDataProvider implements ExternalDataProvider { +public class MockDataProvider extends AbstractExternalDataProvider { private Map mockLookupMap; private String sourceIdentifier; From 6b4261c469d3f1403bf7436778c85d2e727b9dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 22 Sep 2021 10:06:56 +0100 Subject: [PATCH 0298/1254] checkstyle fixes --- .../provider/impl/OpenAIREFundingDataProvider.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java index 429275cea0..cede356d5d 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java @@ -90,8 +90,11 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { try { if (response.getHeader() != null && Integer.parseInt(response.getHeader().getTotal()) > 0) { Project project = response.getResults().getResult().get(0).getMetadata().getEntity().getProject(); - ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider.ExternalDataObjectBuilder( - project).setId(generateProjectURI(project)).setSource(sourceIdentifier).build(); + ExternalDataObject externalDataObject = new OpenAIREFundingDataProvider + .ExternalDataObjectBuilder(project) + .setId(generateProjectURI(project)) + .setSource(sourceIdentifier) + .build(); return Optional.of(externalDataObject); } } catch (NumberFormatException e) { @@ -134,8 +137,11 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { if (projects.size() > 0) { return projects.stream() - .map(project -> new OpenAIREFundingDataProvider.ExternalDataObjectBuilder(project) - .setId(generateProjectURI(project)).setSource(sourceIdentifier).build()) + .map(project -> new OpenAIREFundingDataProvider + .ExternalDataObjectBuilder(project) + .setId(generateProjectURI(project)) + .setSource(sourceIdentifier) + .build()) .collect(Collectors.toList()); } return Collections.emptyList(); From 77048cbe1d5a1d30b924fc62700c8bf35ffbc585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 22 Sep 2021 10:12:54 +0100 Subject: [PATCH 0299/1254] checkstyle fixes --- .../java/org/dspace/external/OpenAIRERestConnector.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 8f36141a2d..5e45d6324d 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -53,8 +53,8 @@ public class OpenAIRERestConnector { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIRERestConnector.class); /** - * OpenAIRE API Url - * and can be configured with: openaire.api.url + * OpenAIRE API Url + * and can be configured with: openaire.api.url */ private String url = "https://api.openaire.eu"; @@ -86,7 +86,7 @@ public class OpenAIRERestConnector { * and can be configured with: openaire.token.clientSecret */ private String clientSecret; - + public OpenAIRERestConnector(String url) { this.url = url; From 74e47841f44568de98035ff9b57a46b2fd99ebaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 22 Sep 2021 11:30:05 +0100 Subject: [PATCH 0300/1254] add DSpace license --- .../dspace/app/rest/OpenAIREFundingExternalSourcesIT.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java index d734f08c7a..b8f886b2e4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/OpenAIREFundingExternalSourcesIT.java @@ -1,3 +1,10 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; From 5366e433bde4ecb468e770955bb5401f4f43795d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 22 Sep 2021 11:30:57 +0100 Subject: [PATCH 0301/1254] missing escaping query from getNumberOfResults --- .../impl/OpenAIREFundingDataProvider.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java index cede356d5d..64449de24a 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java @@ -18,13 +18,6 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.content.dto.MetadataValueDTO; -import org.dspace.external.OpenAIRERestConnector; -import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; -import org.springframework.beans.factory.annotation.Autowired; import eu.openaire.jaxb.helper.FundingHelper; import eu.openaire.jaxb.helper.ProjectHelper; import eu.openaire.jaxb.model.Response; @@ -33,6 +26,13 @@ import eu.openaire.oaf.model.base.FunderType; import eu.openaire.oaf.model.base.FundingTreeType; import eu.openaire.oaf.model.base.FundingType; import eu.openaire.oaf.model.base.Project; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.content.dto.MetadataValueDTO; +import org.dspace.external.OpenAIRERestConnector; +import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.ExternalDataProvider; +import org.springframework.beans.factory.annotation.Autowired; /** * This class is the implementation of the ExternalDataProvider interface that @@ -154,7 +154,10 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { @Override public int getNumberOfResults(String query) { - Response projectResponse = connector.searchProjectByKeywords(0, 0, query); + // escaping query + String encodedQuery = encodeValue(query); + + Response projectResponse = connector.searchProjectByKeywords(0, 0, encodedQuery); return Integer.parseInt(projectResponse.getHeader().getTotal()); } From cb40adaf431c07a2a91d892b55719bd7d97e3a18 Mon Sep 17 00:00:00 2001 From: Yura Date: Wed, 22 Sep 2021 12:30:07 +0200 Subject: [PATCH 0302/1254] 83695: End REST process execution after handling exceptions --- .../handler/DSpaceRunnableHandler.java | 7 ++++++ .../CommandLineDSpaceRunnableHandler.java | 6 +++++ .../rest/repository/ScriptRestRepository.java | 12 ++++++---- .../impl/RestDSpaceRunnableHandler.java | 23 +++++++++++-------- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java index 078ba6bfa2..f1b37cade2 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java @@ -77,6 +77,13 @@ public interface DSpaceRunnableHandler { */ public void logError(String message); + /** + * This method will perform the error logging of the message given along with a stack trace + * @param message The message to be logged as an error + * @param throwable The original exception + */ + public void logError(String message, Throwable throwable); + /** * This method will print the help for the options and name * @param options The options for the script diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java index 6775b9a455..6a108728d4 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java @@ -84,6 +84,12 @@ public class CommandLineDSpaceRunnableHandler implements DSpaceRunnableHandler { log.error(message); } + @Override + public void logError(String message, Throwable throwable) { + System.err.println(message); + log.error(message, throwable); + } + @Override public void printHelp(Options options, String name) { if (options != null) { 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 661930c560..eb2afc0e54 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 @@ -152,10 +152,14 @@ public class ScriptRestRepository extends DSpaceRestRepository Date: Wed, 22 Sep 2021 16:27:50 +0200 Subject: [PATCH 0303/1254] [CST-4507] endpoint to find with a single call many authorizations for multiple objects and multiple features --- .../AuthorizationRestRepository.java | 101 +- .../java/org/dspace/app/rest/utils/Utils.java | 42 +- .../rest/AuthorizationRestRepositoryIT.java | 875 ++++++++++++++++++ 3 files changed, 1002 insertions(+), 16 deletions(-) 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 704a4191dd..3b308a02d6 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 @@ -15,6 +15,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import org.apache.commons.lang3.ObjectUtils; import org.dspace.app.rest.Parameter; @@ -154,12 +155,8 @@ public class AuthorizationRestRepository extends DSpaceRestRepository authorizations = findAuthorizationsForUri(context, user, uri, featureName); + + if (currUser != user) { + // restore the real current user + context.restoreContextUser(); + } + return converter.toRestPage(authorizations, pageable, utils.obtainProjection()); + } + + @PreAuthorize("#epersonUuid==null || hasPermission(#epersonUuid, 'EPERSON', 'READ')") + @SearchRestMethod(name = "objects") + public Page findByObjects(@Parameter(value = "uuid") List uuidList, + @Parameter(value = "type") String type, + @Parameter(value = "eperson") UUID epersonUuid, @Parameter(value = "feature") List featureNames, + Pageable pageable) throws AuthorizeException, SQLException { + + Context context = obtainContext(); + + EPerson currUser = context.getCurrentUser(); + + // get the user specified in the requested parameters, can be null for anonymous + EPerson user = getUserFromRequestParameter(context, epersonUuid); + if (ObjectUtils.notEqual(currUser, user)) { + // Temporarily change the Context's current user in order to retrieve + // authorizations based on that user + context.switchContextUser(user); + } + + List authorizations = + findAuthorizationsByUUIDList(context, type, uuidList, user, featureNames); + + if (currUser != user) { + // restore the real current user + context.restoreContextUser(); + } + return converter.toRestPage(authorizations, null, utils.obtainProjection()); + } + + private List findAuthorizationsByUUIDList( + Context context, + String type, List uuidList, EPerson user, + List featureNames) { + + if (featureNames.isEmpty()) { + return new ArrayList<>(); + } + + List authorizations = new ArrayList<>(); + + List objects = uuidList.stream() + .map(uuid -> utils.getBaseObjectRestFromTypeAndUUID(context, type, uuid)) + .collect(Collectors.toList()); + + objects.forEach(object -> + featureNames.forEach(featureName -> { + try { + authorizations.addAll(authorizationsForObject(context, user, featureName, object)); + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + throw new RuntimeException(ex); + } + })); + return authorizations; + } + + private List findAuthorizationsForUri( + Context context, + EPerson user, + String uri, + String featureName) throws SQLException { + + BaseObjectRest restObject = utils.getBaseObjectRestFromUri(context, uri); + return authorizationsForObject(context, user, featureName, restObject); + } + + private List authorizationsForObject( + Context context, + EPerson user, String featureName, + BaseObjectRest obj) + throws SQLException { + + if (obj == null) { + return new ArrayList<>(); + } + List authorizations; if (isNotBlank(featureName)) { authorizations = findByObjectAndFeature(context, user, obj, featureName); } else { - List features = authorizationFeatureService.findByResourceType(obj.getUniqueType()); + List features = + authorizationFeatureService.findByResourceType(obj.getUniqueType()); authorizations = new ArrayList<>(); for (AuthorizationFeature f : features) { if (authorizationFeatureService.isAuthorized(context, f, obj)) { @@ -181,11 +264,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository findByObjectAndFeature( 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 1dbb08a3b3..534e4aa907 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 @@ -964,22 +964,54 @@ public class Utils { throw new IllegalArgumentException("the supplied uri lacks required path elements: " + uri); } + return findBaseObjectRest(context, uriParts[0], uriParts[1], uriParts[2]); + } + + /** + * Gets the rest object of of specified type (i.e. "core.item", "core.collection", "workflow.workflowitem",...) + * having given uuid. + * + * @param context the DSpace context + * @param type Object type + * @param uuid Object uuid + * @return the {@link BaseObjectRest} identified by the provided uuid + */ + + public BaseObjectRest getBaseObjectRestFromTypeAndUUID(Context context, String type, String uuid) { + + if (StringUtils.isBlank(type)) { + throw new IllegalArgumentException("Type is missing"); + } + + String[] split = type.split("\\."); + if (split.length != 2) { + throw new IllegalArgumentException("Provided type is not valid: " + type); + } + return findBaseObjectRest(context, split[0], split[1], uuid); + } + + private BaseObjectRest findBaseObjectRest(Context context, String apiCategory, String model, String uuid) { + DSpaceRestRepository repository; try { - repository = getResourceRepository(uriParts[0], uriParts[1]); + repository = getResourceRepository(apiCategory, model); if (!(repository instanceof ReloadableEntityObjectRepository)) { - throw new IllegalArgumentException("the supplied uri is not for the right type of repository: " + uri); + throw new IllegalArgumentException("the supplied category and model are not" + + " for the right type of repository: " + + String.format("%s.%s", apiCategory, model)); } } catch (RepositoryNotFoundException e) { - throw new IllegalArgumentException("the repository for the URI '" + uri + "' was not found", e); + throw new IllegalArgumentException("the repository for the category '" + apiCategory + "' and model '" + + model + "' was not found", e); } Serializable pk; try { // cast the string id in the uriParts to the real pk class - pk = castToPKClass((ReloadableEntityObjectRepository) repository, uriParts[2]); + pk = castToPKClass((ReloadableEntityObjectRepository) repository, uuid); } catch (Exception e) { - throw new IllegalArgumentException("the supplied uri could not be cast to a Primary Key class: " + uri, e); + throw new IllegalArgumentException( + "the supplied uuid could not be cast to a Primary Key class: " + uuid, e); } try { // disable the security as we only need to retrieve the object to further process the authorization 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 08564852ee..fb8b89198a 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 @@ -10,6 +10,7 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; @@ -34,6 +35,7 @@ import org.dspace.app.rest.authorization.TrueForTestUsersFeature; import org.dspace.app.rest.authorization.TrueForUsersInGroupTestFeature; import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.converter.EPersonConverter; +import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.BaseObjectRest; @@ -44,10 +46,14 @@ import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.Item; import org.dspace.content.Site; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.SiteService; @@ -83,6 +89,8 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration private CommunityConverter communityConverter; @Autowired private ConfigurationService configurationService; + @Autowired + private ItemConverter itemConverter; @Autowired private Utils utils; @@ -1517,6 +1525,873 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isInternalServerError()); } + @Test + /** + * Verify that the search by multiple objects and features works properly in allowed scenarios: + * - for an administrator + * - for an administrator that want to inspect permission of the anonymous users or another user + * - for a logged-in "normal" user + * - for anonymous + * + * @throws Exception + */ + public void findByMultipleObjectsAndFeaturesTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community com = CommunityBuilder.createCommunity(context).withName("A test community").build(); + String comId = com.getID().toString(); + CommunityRest comRest = communityConverter.convert(com, DefaultProjection.DEFAULT); + Community secondCom = CommunityBuilder.createCommunity(context).withName("Another test community").build(); + String secondComId = secondCom.getID().toString(); + CommunityRest secondComRest = communityConverter.convert(secondCom, DefaultProjection.DEFAULT); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // verify that it works for administrators - with eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.community") + .param("uuid", comId) + .param("uuid", secondComId) + .param("projection", "level") + .param("embedLevelDepth", "1") + .param("feature", alwaysTrue.getName()) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName()) + .param("eperson", admin.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( + allOf( + hasJsonPath("$.id", is(admin.getID().toString() + "_" + alwaysTrue.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId())), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is(admin.getID().toString() + "_" + trueForAdmins.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId())), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForAdmins.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is(admin.getID().toString() + "_" + alwaysTrue.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId())), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is(admin.getID().toString() + "_" + trueForAdmins.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId())), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForAdmins.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ) + ))); + + // verify that it works for administrators - without eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.community") + .param("uuid", comId) + .param("uuid", secondComId) + .param("projection", "level") + .param("embedLevelDepth", "1") + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName()) + .param("feature", alwaysTrue.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( + allOf( + hasJsonPath("$.id", is( + admin.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + admin.getID().toString() + "_" + + trueForAdmins.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForAdmins.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + admin.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + admin.getID().toString() + "_" + + trueForAdmins.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForAdmins.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ) + ))); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // verify that it works for normal loggedin users - with eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.community") + .param("uuid", comId) + .param("uuid", secondComId) + .param("projection", "level") + .param("embedLevelDepth", "1") + .param("feature", alwaysTrue.getName()) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ) + ))); + + // verify that it works for normal loggedin users - without eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.community") + .param("uuid", comId) + .param("uuid", secondComId) + .param("projection", "level") + .param("embedLevelDepth", "1") + .param("feature", alwaysFalse.getName()) + .param("feature", trueForLoggedUsers.getName()) + .param("feature", alwaysTrue.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + trueForLoggedUsers.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + trueForLoggedUsers.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ) + ))); + + // verify that it works for administators inspecting other users - by using the eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.community") + .param("uuid", comId) + .param("uuid", secondComId) + .param("projection", "level") + .param("embedLevelDepth", "1") + .param("feature", alwaysTrue.getName()) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName()) + .param("feature", trueForLoggedUsers.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + trueForLoggedUsers.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + trueForLoggedUsers.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ) + ))); + + // verify that it works for administators inspecting other users - by assuming login + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.community") + .param("uuid", comId) + .param("uuid", secondComId) + .param("projection", "level") + .param("embedLevelDepth", "1") + .param("feature", alwaysTrue.getName()) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName()) + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.authorizations", contains( + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + alwaysTrue.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ) + ))); + + // verify that it works for anonymous users + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.community") + .param("uuid", comId) + .param("uuid", secondComId) + .param("projection", "level") + .param("embedLevelDepth", "1") + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName()) + .param("feature", alwaysTrue.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( + allOf( + hasJsonPath("$.id", is( + alwaysTrue.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson", nullValue()) + ), + allOf( + hasJsonPath("$.id", is( + alwaysTrue.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), + hasJsonPath("$._embedded.eperson", nullValue()) + ) + ))); + } + + @Test + /** + * Verify that the search by many objects and features works return 204 No Content when no feature is granted. + * + * @throws Exception + */ + public void findByMultipleObjectsAndFeatureNotGrantedTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context,community).build(); + Item item = ItemBuilder.createItem(context, collection).build(); + String itemId = item.getID().toString(); + ItemRest itemRest = itemConverter.convert(item, DefaultProjection.DEFAULT); + Item secondItem = ItemBuilder.createItem(context, collection).build(); + String secondItemId = secondItem.getID().toString(); + ItemRest secondItemRest = itemConverter.convert(secondItem, DefaultProjection.DEFAULT); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // verify that it works for administrators - with eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", itemId) + .param("uuid", secondItemId) + .param("feature", alwaysFalse.getName()) + .param("feature", alwaysFalse.getName()) + .param("eperson", admin.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administrators - without eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", itemId) + .param("uuid", secondItemId) + .param("feature", alwaysFalse.getName()) + .param("feature", alwaysFalse.getName())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // verify that it works for normal loggedin users - with eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", itemId) + .param("uuid", secondItemId) + .param("feature", alwaysFalse.getName()) + .param("feature", alwaysFalse.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for normal loggedin users - without eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", itemId) + .param("uuid", secondItemId) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administators inspecting other users - by using the eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", itemId) + .param("uuid", secondItemId) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administators inspecting other users - by assuming login + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", itemId) + .param("uuid", secondItemId) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForAdmins.getName()) + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for anonymous users + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", itemId) + .param("uuid", secondItemId) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForLoggedUsers.getName())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + /** + * Verify that the find by multiple objects and features + * return the 204 No Content code when the requested object doesn't exist but the uri + * is potentially valid (i.e. deleted object) or the feature doesn't exist + * + * @throws Exception + */ + public void findByNotExistingMultipleObjectsAndFeatureTest() throws Exception { + UUID wrongSiteId = UUID.randomUUID(); + String siteId = wrongSiteId.toString(); + String wrongSiteUri = "http://localhost/api/core/sites/" + wrongSiteId; + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); + + // disarm the alwaysThrowExceptionFeature + configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // verify that it works for administrators, no result - with eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysTrue.getName()) + .param("eperson", admin.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-feature") + .param("eperson", admin.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administrators, no result - without eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysTrue.getName())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", "not-existing-feature")) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // verify that it works for normal loggedin users - with eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", alwaysTrue.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", "not-existing-feature") + .param("eperson", eperson.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for normal loggedin users - without eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", alwaysTrue.getName())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", "not-existing-feature")) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administators inspecting other users - by using the eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", alwaysTrue.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", "not-existing-feature") + .param("eperson", eperson.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for administators inspecting other users - by assuming login + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", alwaysTrue.getName()) + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", "not-existing-feature") + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + // verify that it works for anonymous users + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", alwaysTrue.getName())) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", "not-existing-one") + .param("feature", "not-existing-feature")) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + /** + * Verify that the find by multiple objects and features + * return the 400 Bad Request response for invalid or missing UUID or type + * + * @throws Exception + */ + public void findByMultipleObjectsAndFeatureBadRequestTest() throws Exception { + String[] invalidUris = new String[] { + "foo", + "", + "boo-invalid", + "this-is-not-an-uuid" + }; + // disarm the alwaysThrowExceptionFeature + configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); +// for (String invalidUri : invalidUris) { +// log.debug("findByObjectAndFeatureBadRequestTest - Testing the URI: " + invalidUri); + + // verify that it works for administrators with an invalid or missing uuid - with eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", "foo") + .param("uuid", "") + .param("uuid", "invalid") + .param("uuid", "this-is-not-an-uuid") + .param("feature", alwaysTrue.getName()) + .param("eperson", admin.getID().toString())) + .andExpect(status().isBadRequest()); + + // verify that it works for administrators with an invalid or missing uuid - without eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", "foo") + .param("uuid", "") + .param("uuid", "invalid") + .param("uuid", "this-is-not-an-uuid") + .param("feature", alwaysTrue.getName())) + .andExpect(status().isBadRequest()); + + // verify that it works for normal loggedin users with an invalid or missing uuid - with eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", "foo") + .param("uuid", "") + .param("uuid", "invalid") + .param("uuid", "this-is-not-an-uuid") + .param("feature", alwaysTrue.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isBadRequest()); + + // verify that it works for normal loggedin users with an invalid or missing + // uuid - without eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", "foo") + .param("uuid", "") + .param("uuid", "invalid") + .param("uuid", "this-is-not-an-uuid") + .param("feature", alwaysTrue.getName())) + .andExpect(status().isBadRequest()); + + // verify that it works for administators inspecting other users with an invalid or missing uri - by + // using the eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", "foo") + .param("uuid", "") + .param("uuid", "invalid") + .param("uuid", "this-is-not-an-uuid") + .param("feature", alwaysTrue.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isBadRequest()); + + // verify that it works for administators inspecting other users with an invalid or missing uri - by + // assuming login + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", "foo") + .param("uuid", "") + .param("uuid", "invalid") + .param("uuid", "this-is-not-an-uuid") + .param("feature", alwaysTrue.getName()) + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(status().isBadRequest()); + + // verify that it works for anonymous users with an invalid or missing uri + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("uuid", "foo") + .param("uuid", "") + .param("uuid", "invalid") + .param("uuid", "this-is-not-an-uuid") + .param("feature", alwaysTrue.getName())) + .andExpect(status().isBadRequest()); + + // verify that returns bad request for invalid type + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "INVALID") + .param("uuid", UUID.randomUUID().toString()) + .param("uuid", UUID.randomUUID().toString()) + .param("feature", alwaysTrue.getName())) + .andExpect(status().isBadRequest()); + + // verify that returns bad request for missing type + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("uuid", UUID.randomUUID().toString()) + .param("uuid", UUID.randomUUID().toString()) + .param("feature", alwaysTrue.getName())) + .andExpect(status().isBadRequest()); +// } + } + + @Test + /** + * Verify that the find by multiple objects and features return + * the 401 Unauthorized response when an eperson is involved + * + * @throws Exception + */ + public void findByMultipleObjectsAndFeatureUnauthorizedTest() throws Exception { + Site site = siteService.findSite(context); + String siteId = site.getID().toString(); + + // disarm the alwaysThrowExceptionFeature + configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); + + // verify that it works for an anonymous user inspecting an admin user - by using the eperson parameter + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysTrue.getName()) + .param("eperson", admin.getID().toString())) + .andExpect(status().isUnauthorized()); + + // verify that it works for an anonymous user inspecting an admin user - by assuming login + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysTrue.getName()) + .header("X-On-Behalf-Of", admin.getID())) + .andExpect(status().isUnauthorized()); + + // verify that it works for an anonymous user inspecting a normal user - by using the eperson parameter + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysTrue.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isUnauthorized()); + + // verify that it works for an anonymous user inspecting a normal user - by assuming login + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysTrue.getName()) + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + /** + * Verify that the find by multiple objects and features + * returns the 403 Forbidden response when a non-admin eperson try to search + * the authorization of another eperson + * + * @throws Exception + */ + public void findByMultipleObjectsAndFeatureForbiddenTest() throws Exception { + Site site = siteService.findSite(context); + String siteId = site.getID().toString(); + + context.turnOffAuthorisationSystem(); + EPerson anotherEperson = EPersonBuilder.createEPerson(context).withEmail("another@example.com") + .withPassword(password).build(); + context.restoreAuthSystemState(); + // disarm the alwaysThrowExceptionFeature + configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); + String anotherToken = getAuthToken(anotherEperson.getEmail(), password); + + // verify that he cannot search the admin authorizations - by using the eperson parameter + getClient(anotherToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysFalse.getName()) + .param("feature", alwaysTrue.getName()) + .param("eperson", admin.getID().toString())) + .andExpect(status().isForbidden()); + + // verify that he cannot search the admin authorizations - by assuming login + getClient(anotherToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysFalse.getName()) + .param("feature", alwaysTrue.getName()) + .header("X-On-Behalf-Of", admin.getID())) + .andExpect(status().isForbidden()); + + // verify that he cannot search the authorizations of another "normal" eperson - by using the eperson parameter + getClient(anotherToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysFalse.getName()) + .param("feature", alwaysTrue.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isForbidden()); + + // verify that he cannot search the authorizations of another "normal" eperson - by assuming login + getClient(anotherToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysFalse.getName()) + .param("feature", alwaysTrue.getName()) + .header("X-On-Behalf-Of", eperson.getID())) + .andExpect(status().isForbidden()); + } + + @Test + /** + * Verify that an exception in the multiple authorization features check will be reported back + * @throws Exception + */ + public void findByMultipleObjectsAndFeatureInternalServerErrorTest() throws Exception { + Site site = siteService.findSite(context); + String siteId = site.getID().toString(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // verify that it works for administrators - with eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysException.getName()) + .param("eperson", admin.getID().toString())) + .andExpect(status().isInternalServerError()); + + // verify that it works for administrators - without eperson parameter + getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysException.getName())) + .andExpect(status().isInternalServerError()); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // verify that it works for normal loggedin users - with eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysException.getName()) + .param("eperson", eperson.getID().toString())) + .andExpect(status().isInternalServerError()); + + // verify that it works for normal loggedin users - without eperson parameter + getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysException.getName())) + .andExpect(status().isInternalServerError()); + + // verify that it works for anonymous users + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.site") + .param("uuid", siteId) + .param("uuid", siteId) + .param("feature", alwaysException.getName())) + .andExpect(status().isInternalServerError()); + } + @Test /** * This test will check that special group are correctly used to verify From d25c74f00162f49fa2bd0d7b7944424296ae872c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Wed, 22 Sep 2021 18:00:29 +0200 Subject: [PATCH 0304/1254] Intermediate commit - refactoring to rely on metadata for feature activation and configuration --- .../org/dspace/builder/BitstreamBuilder.java | 22 ++ .../java/org/dspace/builder/ItemBuilder.java | 25 ++ .../app/rest/iiif/IIIFServiceFacade.java | 5 +- .../iiif/model/generator/CanvasGenerator.java | 4 + .../model/generator/CanvasItemsGenerator.java | 6 +- .../model/generator/ManifestGenerator.java | 17 +- .../generator/MetadataEntryGenerator.java | 13 +- .../iiif/model/generator/RangeGenerator.java | 17 +- .../app/rest/iiif/model/info/Annotation.java | 30 -- .../app/rest/iiif/model/info/Canvas.java | 50 --- .../rest/iiif/model/info/GlobalDefaults.java | 51 --- .../dspace/app/rest/iiif/model/info/Info.java | 42 --- .../app/rest/iiif/model/info/Range.java | 31 -- .../iiif/service/AbstractResourceService.java | 14 +- .../iiif/service/AnnotationListService.java | 38 +-- .../iiif/service/CanvasLookupService.java | 18 +- .../app/rest/iiif/service/CanvasService.java | 80 ++--- .../iiif/service/ImageContentService.java | 2 - .../rest/iiif/service/ManifestService.java | 209 +++++++----- .../app/rest/iiif/service/RangeService.java | 41 +-- .../app/rest/iiif/service/SeeAlsoService.java | 5 +- .../rest/iiif/service/SequenceService.java | 45 +-- .../app/rest/iiif/service/util/IIIFUtils.java | 308 ++++++++---------- .../app/rest/iiif/IIIFControllerIT.java | 186 +++++------ dspace/config/dspace.cfg | 2 +- dspace/config/modules/iiif.cfg | 43 +++ dspace/config/registries/dspace-types.xml | 6 + dspace/config/registries/iiif-types.xml | 66 ++++ 28 files changed, 652 insertions(+), 724 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java create mode 100644 dspace/config/registries/iiif-types.xml diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index da9eddacd5..f98befe57f 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -136,6 +136,27 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return this; } + + public BitstreamBuilder withIIIFLabel(String label) throws SQLException { + bitstreamService.addMetadata(context, bitstream, "iiif", "label", null, null, label); + return this; + } + + public BitstreamBuilder withIIIFCanvasWidth(int i) throws SQLException { + bitstreamService.addMetadata(context, bitstream, "iiif", "image", "width", null, String.valueOf(i)); + return this; + } + + public BitstreamBuilder withIIIFCanvasHeight(int i) throws SQLException { + bitstreamService.addMetadata(context, bitstream, "iiif", "image", "height", null, String.valueOf(i)); + return this; + } + + public BitstreamBuilder withIIIFToC(String toc) throws SQLException { + bitstreamService.addMetadata(context, bitstream, "iiif", "toc", null, null, toc); + return this; + } + private Bundle getOriginalBundle(Item item) throws SQLException, AuthorizeException { List bundles = itemService.getBundles(item, ORIGINAL); Bundle targetBundle = null; @@ -199,4 +220,5 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { protected DSpaceObjectService getService() { return bitstreamService; } + } 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 c4bdbe7d54..831f2a6b00 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -115,6 +115,30 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { return addMetadataValue(item, MetadataSchemaEnum.DC.getName(), "description", "provenance", provenanceData); } + public ItemBuilder enableIIIF() { + return addMetadataValue(item, "dspace", "iiif", "enabled", "true"); + } + + public ItemBuilder disableIIIF() { + return addMetadataValue(item, "dspace", "iiif", "enabled", "false"); + } + + public ItemBuilder enableIIIFSearch() { + return addMetadataValue(item, "iiif", "search", "enabled", "true"); + } + + public ItemBuilder withIIIFCanvasLabel(String label) { + return addMetadataValue(item, "iiif", "canvas", "label", label); + } + + public ItemBuilder withIIIFCanvasWidth(int i) { + return addMetadataValue(item, "iiif", "image", "width", String.valueOf(i)); + } + + public ItemBuilder withIIIFCanvasHeight(int i) { + return addMetadataValue(item, "iiif", "image", "height", String.valueOf(i)); + } + public ItemBuilder withMetadata(final String schema, final String element, final String qualifier, final String value) { return addMetadataValue(item, schema, element, qualifier, value); @@ -220,4 +244,5 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { c.complete(); } } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFServiceFacade.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFServiceFacade.java index 2667708791..22ef973cbf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFServiceFacade.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFServiceFacade.java @@ -14,6 +14,7 @@ import org.dspace.app.rest.iiif.service.AnnotationListService; import org.dspace.app.rest.iiif.service.CanvasLookupService; import org.dspace.app.rest.iiif.service.ManifestService; import org.dspace.app.rest.iiif.service.SearchService; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.content.Item; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; @@ -48,6 +49,8 @@ public class IIIFServiceFacade { @Autowired CanvasLookupService canvasLookupService; + @Autowired + IIIFUtils utils; /** * The manifest response contains sufficient information for the client to initialize itself @@ -71,7 +74,7 @@ public class IIIFServiceFacade { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - if (item == null) { + if (item == null || !utils.isIIIFEnabled(item)) { throw new ResourceNotFoundException("IIIF manifest for id " + id + " not found"); } return manifestService.getManifest(item, context); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index 949efab1ac..808d7bded1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -34,6 +34,10 @@ public class CanvasGenerator implements IIIFResource { return this; } + public String getIdentifier() { + return identifier; + } + /** * Every canvas must have a label to display. * @param label diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java index e0aa59ff7e..869f3b88c0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java @@ -61,8 +61,10 @@ public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.gene * Add a Canvas to the sequence. * @param canvas wrapper for Canvas */ - public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { - this.canvas.add((Canvas) canvas.getResource()); + public String addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + Canvas resource = (Canvas) canvas.getResource(); + this.canvas.add(resource); + return resource.getIdentifier().toString(); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 05b067732f..b94d236993 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -132,9 +132,9 @@ public class ManifestGenerator implements IIIFResource { * @param field property field * @param value property value */ - public void addMetadata(String field, String value) { + public void addMetadata(String field, String value, String... rest) { metadataEntryGenerator.setField(field); - metadataEntryGenerator.setValue(value); + metadataEntryGenerator.setValue(value, rest); metadata.add(metadataEntryGenerator.getValue()); } @@ -148,19 +148,18 @@ public class ManifestGenerator implements IIIFResource { /** * Adds optional description to Manifest. - * @param field property field - * @param value property value + * @param value the description value */ - public void addDescription(String field, String value) { - description = new PropertyValueGenerator().getPropertyValue(field, value).getValue(); + public void addDescription(String value) { + description = new PropertyValueGenerator().getPropertyValue(value).getValue(); } /** * Adds optional Range to the manifest's structures element. - * @param rangeGenerator list of range models + * @param rangeGenerator to add */ - public void setRange(List rangeGenerator) { - ranges = rangeGenerator; + public void addRange(RangeGenerator rangeGenerator) { + ranges.add(rangeGenerator); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java index 53d2987431..0cad9b0163 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.MetadataEntry; +import de.digitalcollections.iiif.model.PropertyValue; import org.springframework.stereotype.Component; @Component @@ -15,6 +16,7 @@ public class MetadataEntryGenerator implements IIIFValue { private String field; private String value; + private String[] rest; /** * Set metadata field name. @@ -28,12 +30,19 @@ public class MetadataEntryGenerator implements IIIFValue { * Set metadata value. * @param value metadata value */ - public void setValue(String value) { + public void setValue(String value, String... rest) { this.value = value; + this.rest = rest; } @Override public MetadataEntry getValue() { - return new MetadataEntry(field, value); + PropertyValue metadataValues; + if (rest != null && rest.length > 0) { + metadataValues = new PropertyValue(value, rest); + } else { + metadataValues = new PropertyValue(value); + } + return new MetadataEntry(new PropertyValue(field), metadataValues); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java index ff92f6a7a6..00575411e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java @@ -20,14 +20,15 @@ import de.digitalcollections.iiif.model.sharedcanvas.Resource; * The rationale for separating ranges from sequences is that there is likely to be overlap between different ranges, * such as the physical structure of a book compared to the textual structure of the work. * - * This is used to populate the "structures" element of the Manifest. (The REST API service looks to the "info.json" - * file for ranges.) + * This is used to populate the "structures" element of the Manifest. The structure is derived from the iiif.toc + * metadata and the ordered sequence of bitstreams (canvases) */ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { private String identifier; private String label; private final List canvasList = new ArrayList<>(); + private final List rangesList = new ArrayList<>(); /** * Sets mandatory range identifier. @@ -38,6 +39,10 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. return this; } + public String getIdentifier() { + return identifier; + } + /** * Sets mandatory range label. * @param label range label @@ -62,6 +67,14 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. for (Canvas canvas : canvasList) { range.addCanvas(canvas); } + for (RangeGenerator rg : rangesList) { + range.addRange((Range) rg.getResource()); + } return range; } + + public void addSubRange(RangeGenerator range) { + range.setIdentifier(identifier + "-" + rangesList.size()); + rangesList.add(range); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.java deleted file mode 100644 index 800c85863f..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Annotation.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.iiif.model.info; - -public class Annotation { - - private String motivation; - private String id; - - public void setMotivation(String motivation) { - this.motivation = motivation; - } - - public String getMotivation() { - return motivation; - } - - public void setID(String id) { - this.id = id; - } - - public String getId() { - return id; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java deleted file mode 100644 index f3d260c92a..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Canvas.java +++ /dev/null @@ -1,50 +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.iiif.model.info; - -public class Canvas { - - private String label; - private int width; - private int height; - private int pos; - - public void setLabel(String label) { - this.label = label; - } - - public String getLabel() { - return label; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getWidth() { - return width; - } - - public void setHeight(int height) { - this.height = height; - } - - public int getHeight() { - return height; - } - - // TODO: These can be removed. - public void setPos(int pos) { - this.pos = pos; - } - - public int getPos() { - return pos; - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java deleted file mode 100644 index b8455e6332..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/GlobalDefaults.java +++ /dev/null @@ -1,51 +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.iiif.model.info; - -public class GlobalDefaults { - - private boolean activated; - private String label; - private int width; - private int height; - - public boolean isActivated() { - return activated; - } - - public void setActivated(boolean activated) { - this.activated = activated; - } - - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public int getWidth() { - return width; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java deleted file mode 100644 index dcebadfe2c..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Info.java +++ /dev/null @@ -1,42 +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.iiif.model.info; - -import java.util.List; - -public class Info { - - private List canvases; - private List structures; - private GlobalDefaults globalDefaults; - - public GlobalDefaults getGlobalDefaults() { - return globalDefaults; - } - - public void setGlobalDefaults(GlobalDefaults globalDefaults) { - this.globalDefaults = globalDefaults; - } - - public void setCanvases(List canvases) { - this.canvases = canvases; - } - - public List getCanvases() { - return this.canvases; - } - - public void setStructures(List structures) { - this.structures = structures; - } - - public List getStructures() { - return structures; - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java deleted file mode 100644 index 45e819c57d..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/info/Range.java +++ /dev/null @@ -1,31 +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.iiif.model.info; - -public class Range { - - private String label; - private int start; - - public void setLabel(String label) { - this.label = label; - } - - public String getLabel() { - return label; - } - - public void setStart(int start) { - this.start = start; - } - - public int getStart() { - return start; - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 59e9a20be3..b027a428d1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -28,20 +28,20 @@ public abstract class AbstractResourceService { protected String CLIENT_URL; protected String IIIF_LOGO_IMAGE; protected String BITSTREAM_PATH_PREFIX; + protected int DEFAULT_CANVAS_WIDTH; + protected int DEFAULT_CANVAS_HEIGHT; /** * Possible values: "paged" or "individuals". The property * value is set in dspace configuration. */ protected static String DOCUMENT_VIEWING_HINT; - // The DSpace bundle used for IIIF entity types. - protected static final String IIIF_BUNDLE = "IIIF"; - // The DSpace bundle for other content related to item. - protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; - // Paths for IIIF Image API requests. protected static final String THUMBNAIL_PATH = "/full/90,/0/default.jpg"; protected static final String IMAGE_PATH = "/full/full/0/default.jpg"; + // Default canvas dimensions. + protected static final Integer DEFAULT_CANVAS_WIDTH_FALLBACK = 1200; + protected static final Integer DEFAULT_CANVAS_HEIGHT_FALLBACK = 1600; @Autowired IIIFUtils utils; @@ -65,6 +65,10 @@ public abstract class AbstractResourceService { DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint"); CLIENT_URL = configurationService.getProperty("dspace.ui.url"); IIIF_LOGO_IMAGE = configurationService.getProperty("iiif.logo.image"); + DEFAULT_CANVAS_WIDTH = configurationService.getIntProperty("iiif.canvas.default-width", + DEFAULT_CANVAS_WIDTH_FALLBACK); + DEFAULT_CANVAS_HEIGHT = configurationService.getIntProperty("iiif.canvas.default-heigth", + DEFAULT_CANVAS_HEIGHT_FALLBACK); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java index 4b35249dca..d634615150 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java @@ -17,7 +17,6 @@ import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; -import org.dspace.content.Bundle; import org.dspace.content.Item; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; @@ -84,28 +83,23 @@ public class AnnotationListService extends AbstractResourceService { // AnnotationList requires an identifier. annotationList.setIdentifier(IIIF_ENDPOINT + id + "/manifest/seeAlso"); - // Get the "OtherContent" bundle for the item. Add - // Annotations for each bitstream found in the bundle. - List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); - if (bundles.size() > 0) { - for (Bundle bundle : bundles) { - List bitstreams = bundle.getBitstreams(); - for (Bitstream bitstream : bitstreams) { - BitstreamFormat format; - String mimetype; - try { - format = bitstream.getFormat(context); - mimetype = format.getMIMEType(); - } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } - AnnotationGenerator annotation = applicationContext - .getBean(AnnotationGenerator.class, IIIF_ENDPOINT + bitstream.getID() - + "/annot", AnnotationGenerator.LINKING); - annotation.setResource(getLinksGenerator(mimetype, bitstream)); - annotationList.addResource(annotation); - } + // Get the "seeAlso" bitstreams for the item. Add + // Annotations for each bitstream found. + List bitstreams = utils.getSeeAlsoBitstreams(item); + for (Bitstream bitstream : bitstreams) { + BitstreamFormat format; + String mimetype; + try { + format = bitstream.getFormat(context); + mimetype = format.getMIMEType(); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); } + AnnotationGenerator annotation = applicationContext + .getBean(AnnotationGenerator.class, IIIF_ENDPOINT + bitstream.getID() + + "/annot", AnnotationGenerator.LINKING); + annotation.setResource(getLinksGenerator(mimetype, bitstream)); + annotationList.addResource(annotation); } return utils.asJson(annotationList.getResource()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java index c22ea91627..9a83cdf0d2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -7,10 +7,9 @@ */ package org.dspace.app.rest.iiif.service; -import java.util.UUID; +import java.sql.SQLException; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; -import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.content.Bitstream; import org.dspace.content.Item; @@ -40,17 +39,18 @@ public class CanvasLookupService extends AbstractResourceService { public String generateCanvas(Context context, Item item, String canvasId) { int canvasPosition = utils.getCanvasId(canvasId); - Bitstream bitstream = utils.getBitstreamForCanvas(item, IIIF_BUNDLE, canvasPosition); + Bitstream bitstream = utils.getBitstreamForCanvas(context, item, canvasPosition); if (bitstream == null) { throw new ResourceNotFoundException(); } - Info info = - utils.validateInfoForSingleCanvas(utils.getInfo(context, item, IIIF_BUNDLE), canvasPosition); - UUID bitstreamId = bitstream.getID(); String mimeType = utils.getBitstreamMimeType(bitstream, context); - CanvasGenerator canvasGenerator = - canvasService.getCanvas(item.getID().toString(), bitstreamId, mimeType, info, canvasPosition); - + CanvasGenerator canvasGenerator; + try { + canvasGenerator = canvasService.getCanvas(item.getID().toString(), bitstream, bitstream.getBundles().get(0), + item, canvasPosition, mimeType); + } catch (SQLException e) { + throw new RuntimeException(e); + } return utils.asJson(canvasGenerator.getResource()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index c43cdbcaa7..23e1f369f9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -12,7 +12,10 @@ import java.util.UUID; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; -import org.dspace.app.rest.iiif.model.info.Info; +import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; @@ -24,15 +27,15 @@ public class CanvasService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CanvasService.class); - // Default canvas dimensions. - protected static final Integer DEFAULT_CANVAS_WIDTH = 1200; - protected static final Integer DEFAULT_CANVAS_HEIGHT = 1600; - @Autowired ImageContentService imageContentService; + @Autowired + IIIFUtils utils; + /** * Constructor. + * * @param configurationService the DSpace configuration service. */ public CanvasService(ConfigurationService configurationService) { @@ -40,67 +43,44 @@ public class CanvasService extends AbstractResourceService { } /** - * Creates a single Canvas object. If canvas parameters are provided by the - * Info object they are used. If canvas parameters are unavailable, default values + * Creates a single Canvas object. If canvas parameters are provided by the Info + * object they are used. If canvas parameters are unavailable, default values * are used instead. * - * Note that info.json is going to be replaced with metadata in the bitstream DSO. + * Note that info.json is going to be replaced with metadata in the bitstream + * DSO. * - * @param manifestId manifest id + * @param manifestId manifest id * @param bitstreamId uuid of the bitstream - * @param mimeType the mimetype of the bitstream - * @param info parameters for this canvas - * @param count the canvas position in the sequence. + * @param mimeType the mimetype of the bitstream + * @param info parameters for this canvas + * @param count the canvas position in the sequence. * @return canvas object */ - protected CanvasGenerator getCanvas(String manifestId, UUID bitstreamId, String mimeType, Info info, int count) { + protected CanvasGenerator getCanvas(String manifestId, Bitstream bitstream, Bundle bundle, Item item, int count, + String mimeType) { int pagePosition = count + 1; - // Defaults settings. Used if no info.json is provided. - String label = "Page " + pagePosition; - int canvasWidth = DEFAULT_CANVAS_WIDTH; - int canvasHeight = DEFAULT_CANVAS_HEIGHT; + String label = utils.getIIIFLabel(bitstream, "Page " + pagePosition); + int canvasWidth = utils.getCanvasWidth(bitstream, bundle, item, DEFAULT_CANVAS_WIDTH); + int canvasHeight = utils.getCanvasHeight(bitstream, bundle, item, DEFAULT_CANVAS_HEIGHT); + UUID bitstreamId = bitstream.getID(); - // Override with settings from info.json, if available. - if (info != null && info.getGlobalDefaults() != null && info.getCanvases() != null) { - // The info.json file can request global defaults for canvas - // height, width and labels. Use global settings if activated in info.json. - if (info.getGlobalDefaults().isActivated()) { - // Create unique label by appending position to the default label. - label = info.getGlobalDefaults().getLabel() + " " + pagePosition; - canvasWidth = info.getGlobalDefaults().getWidth(); - canvasHeight = info.getGlobalDefaults().getHeight(); - } else if (info.getCanvases().get(count) != null) { - if (info.getCanvases().get(count).getLabel().length() > 0) { - // Labels assumed unique and are not incremented - // when info.json provides individual canvas metadata. - label = info.getCanvases().get(count).getLabel(); - } - canvasWidth = info.getCanvases().get(count).getWidth(); - canvasHeight = info.getCanvases().get(count).getHeight(); - } - } else { - log.info("Correctly formatted info.json was not found for item. Using application defaults."); - } + ImageContentGenerator image = imageContentService.getImageContent(bitstreamId, mimeType, + imageUtil.getImageProfile(), IMAGE_PATH); - ImageContentGenerator image = imageContentService - .getImageContent(bitstreamId, mimeType, imageUtil.getImageProfile(), IMAGE_PATH); - - ImageContentGenerator thumb = imageContentService - .getImageContent(bitstreamId, mimeType, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); + ImageContentGenerator thumb = imageContentService.getImageContent(bitstreamId, mimeType, + thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) - .addImage(image.getResource()) - .addThumbnail(thumb.getResource()) - .setHeight(canvasHeight) - .setWidth(canvasWidth) - .setLabel(label); + .addImage(image.getResource()).addThumbnail(thumb.getResource()).setHeight(canvasHeight) + .setWidth(canvasWidth).setLabel(label); } - /** * Ranges expect the Canvas object to have only an identifier. - * @param identifier the DSpace item identifier + * + * @param identifier the DSpace item identifier * @param startCanvas the position of the canvas in list * @return */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java index 014fc83bf9..476ad27474 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest.iiif.service; -import java.util.Date; import java.util.UUID; import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; @@ -23,7 +22,6 @@ public class ImageContentService extends AbstractResourceService { public ImageContentService(ConfigurationService configurationService) { - System.out.println("ImageContentService " + new Date().toString()); setConfiguration(configurationService); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index b413497190..e41f9f37f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -7,16 +7,21 @@ */ package org.dspace.app.rest.iiif.service; -import java.util.Date; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; -import org.dspace.app.rest.iiif.model.info.Info; -import org.dspace.app.rest.iiif.model.info.Range; +import org.dspace.app.rest.iiif.model.generator.RangeGenerator; import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.util.service.MetadataExposureService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -67,13 +72,17 @@ public class ManifestService extends AbstractResourceService { @Autowired ManifestGenerator manifestGenerator; + @Autowired + MetadataExposureService metadataExposureService; + protected String[] METADATA_FIELDS; /** * Constructor. * @param configurationService the DSpace configuration service. */ public ManifestService(ConfigurationService configurationService) { setConfiguration(configurationService); + METADATA_FIELDS = configurationService.getArrayProperty("iiif.metadata.item"); } /** @@ -98,59 +107,125 @@ public class ManifestService extends AbstractResourceService { private void populateManifest(Item item, Context context) { // If an IIIF bundle is found it will be used. Otherwise, // images in the ORIGINAL bundle will be used. - List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); - List bitstreams = utils.getBitstreams(bundles); - Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); - manifestGenerator.setIdentifier(getManifestId(item.getID())); + List bundles = utils.getIiifBundles(item); + String manifestId = getManifestId(item.getID()); + manifestGenerator.setIdentifier(manifestId); manifestGenerator.setLabel(item.getName()); setLogoContainer(); addRelated(item); addSearchService(item); - addMetadata(item.getMetadata()); - addViewingHint(bitstreams.size()); - addThumbnail(bundles, context); - addSequence(item, bitstreams, context, info); - addRanges(info, item.getID().toString()); + addMetadata(context, item); + addViewingHint(item); + addThumbnail(item, context); + +// List seqs = new ArrayList(); + RangeGenerator root = new RangeGenerator(); + root.setLabel("Table of Contents"); + root.setIdentifier(manifestId + "/range/r0"); +// seqs.add(root); + manifestGenerator.addRange(root); + + Map tocRanges = new HashMap(); + for (Bundle bnd : bundles) { + String bundleToCPrefix = utils.getIIIFFirstToC(bnd); + RangeGenerator lastRange = root; + for (Bitstream b : utils.getIiifBitstreams(context, bnd)) { + CanvasGenerator canvasId = sequenceService.addCanvas(context, item, bnd, b); + List tocs = utils.getIIIFToCs(b, bundleToCPrefix); + if (tocs.size() > 0) { + for (String toc : tocs) { + RangeGenerator currRange = root; + String[] parts = toc.split(IIIFUtils.TOC_SEPARATOR_REGEX); + String key = ""; + for (int pIdx = 0; pIdx < parts.length; pIdx++) { + if (pIdx > 0) { + key += IIIFUtils.TOC_SEPARATOR; + } + key += parts[pIdx]; + if (tocRanges.get(key) != null) { + currRange = tocRanges.get(key); + } else { + // create the sub range + RangeGenerator range = new RangeGenerator(); + range.setLabel(parts[pIdx]); + range.addCanvas( + canvasService.getRangeCanvasReference(manifestId, canvasId.getIdentifier())); + + // add the range reference to the currRange so to get an identifier + currRange.addSubRange(rangeService.getRangeReference(range)); + + // add the range to the manifest + manifestGenerator.addRange(range); + + // move the current range + currRange = range; + tocRanges.put(key, range); + } + } + // add the bitstream canvas to the currRange + currRange.addCanvas(canvasId); + lastRange = currRange; + } + } else { + lastRange.addCanvas(canvasService.getRangeCanvasReference(manifestId, canvasId.getIdentifier())); + } + } + } + manifestGenerator.addSequence( + sequenceService.getSequence(item, context)); + //manifestGenerator.setRange(rangeService.getRanges(info, manifestId)); + addSeeAlso(item); } - /** - * Adds a single sequence with canvases and a rendering property (optional). - * @param item DSpace Item - * @param bitstreams list of bitstreams - * @param context the DSpace context - * @return a sequence of canvases - */ - private void addSequence(Item item, List bitstreams, Context context, Info info) { - // After replacing the info object with DSO metadata we might - // update this method to iterate over the bitstreams list, passing - // the individual bitstream and position to revised methods in - // sequenceService and rangeService. But it's hard to try now without more - // work elsewhere. - manifestGenerator.addSequence( - sequenceService.getSequence(item, bitstreams, context, info)); - } - /** * Adds DSpace Item metadata to the manifest. - * - * @param metadata list of DSpace metadata values + * + * @param context the DSpace Context + * @param item the DSpace item */ - private void addMetadata(List metadata) { - for (MetadataValue meta : metadata) { - String field = utils.getMetadataFieldName(meta); - if (field.contentEquals("rights.uri")) { - manifestGenerator.addMetadata(field, meta.getValue()); - manifestGenerator.addLicense(meta.getValue()); - } else if (field.contentEquals("description")) { - // Add manifest description field. - manifestGenerator.addDescription(field, meta.getValue()); - } else { - // Exclude DSpace description.provenance field. - if (!field.contentEquals("description.provenance")) { - // Everything else, add to manifest metadata fields. - manifestGenerator.addMetadata(field, meta.getValue()); + private void addMetadata(Context context, Item item) { + for (String field : METADATA_FIELDS) { + String[] eq = field.split("\\."); + String schema = eq[0]; + String element = eq[1]; + String qualifier = null; + if (eq.length > 2) { + qualifier = eq[2]; + } + List metadata = item.getItemService().getMetadata(item, schema, element, qualifier, + Item.ANY); + List values = new ArrayList(); + for (MetadataValue meta : metadata) { + // we need to perform the check here as the configuration can include jolly + // characters (i.e. dc.description.*) and we need to be sure to hide qualified + // metadata (dc.description.provenance) + try { + if (metadataExposureService.isHidden(context, meta.getMetadataField().getMetadataSchema().getName(), + meta.getMetadataField().getElement(), meta.getMetadataField().getQualifier())) { + continue; + } + } catch (SQLException e) { + throw new RuntimeException(e); } + values.add(meta.getValue()); + } + if (values.size() > 0) { + if (values.size() > 1) { + manifestGenerator.addMetadata(field, values.get(0), + values.subList(1, values.size()).toArray(new String[values.size() - 1])); + } else { + manifestGenerator.addMetadata(field, values.get(0)); + } + } + String descrValue = item.getItemService().getMetadataFirstValue(item, "dc", "description", null, Item.ANY); + if (StringUtils.isNotBlank(descrValue)) { + manifestGenerator.addDescription(descrValue); + } + + String licenseUriValue = item.getItemService().getMetadataFirstValue(item, "dc", "rights", "uri", Item.ANY); + if (StringUtils.isNotBlank(licenseUriValue)) { + manifestGenerator.addLicense(licenseUriValue); } } } @@ -172,12 +247,10 @@ public class ManifestService extends AbstractResourceService { /** * A hint to the client as to the most appropriate method of displaying the resource. * - * @param bitstreamCount count of bitstreams in the IIIF bundle. + * @param item the DSpace Item */ - private void addViewingHint(int bitstreamCount) { - if (bitstreamCount > 2) { - manifestGenerator.addViewingHint(DOCUMENT_VIEWING_HINT); - } + private void addViewingHint(Item item) { + manifestGenerator.addViewingHint(utils.getIIIFViewingHint(item, DOCUMENT_VIEWING_HINT)); } /** @@ -192,10 +265,7 @@ public class ManifestService extends AbstractResourceService { * @param item the DSpace Item. */ private void addSeeAlso(Item item) { - List bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); - if (bundles.size() > 0) { - manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item, bundles)); - } + manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item)); } /** @@ -215,33 +285,18 @@ public class ManifestService extends AbstractResourceService { } /** - * Adds Ranges to manifest structures element. - * Ranges are defined in the info.json file. - * @param info - * @param identifier - */ - private void addRanges(Info info, String identifier) { - List rangesFromConfig = utils.getRangesFromInfoObject(info); - if (rangesFromConfig != null) { - manifestGenerator.setRange(rangeService.getRanges(info, identifier)); - } - } - - /** - * Adds thumbnail to the manifest. Uses first image in bundle. - * @param bundles image bundles + * Adds thumbnail to the manifest. Uses first image in the manifest. + * @param item the DSpace Item * @param context DSpace context */ - private void addThumbnail(List bundles, Context context) { - List bitstreams = utils.getBitstreams(bundles); + private void addThumbnail(Item item, Context context) { + List bitstreams = utils.getIiifBitstreams(context, item); if (bitstreams != null && bitstreams.size() > 0) { String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context); - if (utils.checkImageMimeType(mimeType)) { - ImageContentGenerator image = imageContentService - .getImageContent(bitstreams.get(0).getID(), mimeType, - thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); - manifestGenerator.addThumbnail(image); - } + ImageContentGenerator image = imageContentService + .getImageContent(bitstreams.get(0).getID(), mimeType, + thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); + manifestGenerator.addThumbnail(image); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java index 76de7d7643..6d60b99468 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java @@ -7,12 +7,7 @@ */ package org.dspace.app.rest.iiif.service; -import java.util.ArrayList; -import java.util.List; - import org.dspace.app.rest.iiif.model.generator.RangeGenerator; -import org.dspace.app.rest.iiif.model.info.Info; -import org.dspace.app.rest.iiif.model.info.Range; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -30,36 +25,12 @@ public class RangeService extends AbstractResourceService { } /** - * Adds Ranges to manifest structures element. - * Ranges are defined in the info.json file. - * @param info - * @param identifier + * Ranges expect the Sub range object to have only an identifier. + * + * @param range the sub range to reference + * @return RangeGenerator able to create the reference */ - public List getRanges(Info info, String identifier) { - List ranges = new ArrayList<>(); - List rangesFromConfig = utils.getRangesFromInfoObject(info); - if (rangesFromConfig != null) { - for (int pos = 0; pos < rangesFromConfig.size(); pos++) { - ranges.add(getRange(identifier, rangesFromConfig.get(pos), pos)); - } - } - return ranges; - } - - /** - * Sets properties on the Range. - * @param identifier DSpace item id - * @param range range from info.json configuration - * @param pos list position of the range - */ - private RangeGenerator getRange(String identifier, Range range, int pos) { - String id = IIIF_ENDPOINT + identifier + "/r" + pos; - String label = range.getLabel(); - RangeGenerator rangeGenerator = new RangeGenerator(); - rangeGenerator.setIdentifier(id); - rangeGenerator.setLabel(label); - String startCanvas = utils.getCanvasId(range.getStart()); - rangeGenerator.addCanvas(canvasService.getRangeCanvasReference(identifier, startCanvas)); - return rangeGenerator; + public RangeGenerator getRangeReference(RangeGenerator range) { + return new RangeGenerator().setIdentifier(range.getIdentifier()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java index 79a8bea0d0..5888e4d010 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java @@ -7,11 +7,8 @@ */ package org.dspace.app.rest.iiif.service; -import java.util.List; - import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; -import org.dspace.content.Bundle; import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -31,7 +28,7 @@ public class SeeAlsoService extends AbstractResourceService { @Autowired ExternalLinksGenerator externalLinksGenerator; - public ExternalLinksGenerator getSeeAlso(Item item, List bundles) { + public ExternalLinksGenerator getSeeAlso(Item item) { return externalLinksGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso") .setType(AnnotationGenerator.TYPE) .setLabel(SEE_ALSO_LABEL); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java index ef0d190ac4..e2e140cec1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java @@ -15,7 +15,6 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; -import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -34,6 +33,13 @@ public class SequenceService extends AbstractResourceService { // TODO i18n private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; + /* + * The counter tracks the position of the bitstream in the list and is used to create the canvas identifier. + * The order of bitstreams (and thus page order in documents) is determined by position in the DSpace + * bundle. + */ + int counter = 0; + @Autowired CanvasItemsGenerator sequenceGenerator; @@ -48,12 +54,9 @@ public class SequenceService extends AbstractResourceService { setConfiguration(configurationService); } - public CanvasItemsGenerator getSequence(Item item, List bitstreams, Context context, Info info) { + public CanvasItemsGenerator getSequence(Item item, Context context) { sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); - if (bitstreams.size() > 0) { - addCanvases(context, item, bitstreams, info); - } addRendering(item, context); return sequenceGenerator; @@ -67,29 +70,15 @@ public class SequenceService extends AbstractResourceService { * @param item the DSpace Item * @param bitstreams list of DSpace bitstreams */ - private void addCanvases(Context context, Item item, - List bitstreams, Info info) { - /* - * The counter tracks the position of the bitstream in the list and is used to create the canvas identifier. - * The order of bitstreams (and thus page order in documents) is determined by position in the DSpace - * bundle. - */ - int counter = 0; - if (bitstreams == null || bitstreams.size() == 0) { - throw new RuntimeException("No bitstreams found for " + item.getID() + - ". Cannot add media content to the manifest."); - } - for (Bitstream bitstream : bitstreams) { - UUID bitstreamId = bitstream.getID(); - String mimeType = utils.getBitstreamMimeType(bitstream, context); - if (utils.checkImageMimeType(mimeType)) { - String manifestId = item.getID().toString(); - CanvasGenerator canvasGenerator = - canvasService.getCanvas(manifestId, bitstreamId, mimeType, info, counter); - sequenceGenerator.addCanvas(canvasGenerator); - counter++; - } - } + public CanvasGenerator addCanvas(Context context, Item item, Bundle bnd, Bitstream bitstream) { + UUID bitstreamId = bitstream.getID(); + String mimeType = utils.getBitstreamMimeType(bitstream, context); + String manifestId = item.getID().toString(); + CanvasGenerator canvasGenerator = + canvasService.getCanvas(manifestId, bitstream, bnd, item, counter, mimeType); + String canvasIdentifier = sequenceGenerator.addCanvas(canvasGenerator); + counter++; + return canvasGenerator; } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 03bfe20d48..c8e186d8c2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -7,29 +7,29 @@ */ package org.dspace.app.rest.iiif.service.util; -import java.io.IOException; -import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.ObjectMapperFactory; -import org.dspace.app.rest.iiif.model.info.Info; -import org.dspace.app.rest.iiif.model.info.Range; -import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; import org.dspace.content.Bundle; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.BitstreamService; +import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.license.CreativeCommonsServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -41,6 +41,18 @@ public class IIIFUtils { // The canvas position will be appended to this string. private static final String CANVAS_PATH_BASE = "/canvas/c"; + public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; + public static final String METADATA_IIIF_SEARCH_ENABLED = "iiif.search.enabled"; + public static final String METADATA_IIIF_LABEL = "iiif.label"; + public static final String METADATA_IIIF_DESCRIPTION = "iiif.description"; + public static final String METADATA_IIIF_TOC = "iiif.toc"; + public static final String METADATA_IIIF_VIEWING_HINT = "iiif.viewing.hint"; + public static final String METADATA_IMAGE_WIDTH = "iiif.image.width"; + public static final String METADATA_IMAGE_HEIGTH = "iiif.image.heigth"; + + public static final String TOC_SEPARATOR = "|||"; + public static final String TOC_SEPARATOR_REGEX = "\\|\\|\\|"; + // get module subclass. protected SimpleModule iiifModule = ObjectMapperFactory.getIiifModule(); // Use the object mapper subclass. @@ -50,48 +62,55 @@ public class IIIFUtils { protected BitstreamService bitstreamService; /** - * For IIIF entities, this method returns the bundle assigned to IIIF - * bitstreams. If the item is not an IIIF entity, the default (ORIGINAL) - * bundle list is returned instead. + * This method returns the bundles holding IIIF resources if any. + * If there is no IIIF content available an empty bundle list is returned. * @param item the DSpace item - * @param iiifBundle the name of the IIIF bundle - * @return DSpace bundle + * + * @return list of DSpace bundles with IIIF content */ - public List getIiifBundle(Item item, String iiifBundle) { - boolean iiif = item.getMetadata().stream() - .filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_type")) - .anyMatch(m -> m.getValue().contentEquals("IIIF") || - m.getValue().contentEquals("IIIFSearchable")); + public List getIiifBundles(Item item) { + boolean iiif = isIIIFEnabled(item); List bundles = new ArrayList<>(); if (iiif) { - bundles = item.getBundles(iiifBundle); - if (bundles.size() == 0) { - bundles = item.getBundles("ORIGINAL"); - } + bundles = item.getBundles().stream().filter(b -> isIIIFBundle(b)).collect(Collectors.toList()); } return bundles; } - /** - * Returns the requested bundle. - * @param item DSpace item - * @param name bundle name - * @return - */ - public List getBundle(Item item, String name) { - return item.getBundles(name); + public boolean isIIIFEnabled(Item item) { + return item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")); } - /** - * Returns bitstreams for the first bundle in the list. - * @param bundles list of DSpace bundles - * @return list of bitstreams - */ - public List getBitstreams(List bundles) { - if (bundles != null && bundles.size() > 0) { - return bundles.get(0).getBitstreams(); + private boolean isIIIFBundle(Bundle b) { + return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, + Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", + "BRANDED_PREVIEW", "TEXT") + && b.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); + } + + public List getIiifBitstreams(Context context, Item item) { + List bitstreams = new ArrayList(); + for (Bundle bnd : getIiifBundles(item)) { + bitstreams + .addAll(getIiifBitstreams(context, bnd)); } - return null; + return bitstreams; + } + + public List getIiifBitstreams(Context context, Bundle bundle) { + return bundle.getBitstreams().stream().filter(b -> isIiifBitstream(context, b)) + .collect(Collectors.toList()); + } + + private 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")); } /** @@ -105,101 +124,39 @@ public class IIIFUtils { BitstreamFormat bitstreamFormat = bitstream.getFormat(context); return bitstreamFormat.getMIMEType(); } catch (SQLException e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } return null; } /** - * Checks to see if the item is searchable. Based on the entity type. + * Checks to see if the item is searchable. Based on the {@link #METADATA_IIIF_SEARCH_ENABLED} metadata. * @param item DSpace item - * @return true if searchable + * @return true if the iiif search is enabled */ public boolean isSearchable(Item item) { return item.getMetadata().stream() - .filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_type")) - .anyMatch(m -> m.getValue().contentEquals("IIIFSearchable")); - } - - /** - * Returns a metadata field name. - * @param meta the DSpace metadata value object - * @return field name as string - */ - public String getMetadataFieldName(MetadataValue meta) { - String element = meta.getMetadataField().getElement(); - String qualifier = meta.getMetadataField().getQualifier(); - // Need to distinguish DC type from DSpace relationship.type. - // Setting element to be the schema name. - if (meta.getMetadataField().getMetadataSchema().getName().contentEquals("relationship")) { - qualifier = element; - element = meta.getMetadataField().getMetadataSchema().getName(); - } - String field = element; - // Add qualifier if defined. - if (qualifier != null) { - field = field + "." + qualifier; - } - return field; + .filter(m -> m.getMetadataField().toString('.').contentEquals("iiif.search.enabled")) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")); } /** * Retrives a bitstream based on its position in the IIIF bundle. + * @param context DSpace Context * @param item DSpace Item * @param canvasPosition bitstream position * @return bitstream */ - public Bitstream getBitstreamForCanvas(Item item, String bundleName, int canvasPosition) { - List bundles = item.getBundles(bundleName); - if (bundles.size() == 0) { - return null; - } - List bitstreams = bundles.get(0).getBitstreams(); + public Bitstream getBitstreamForCanvas(Context context, Item item, int canvasPosition) { + List bitstreams = getIiifBitstreams(context, item); try { - return bitstreams.get(canvasPosition); + return bitstreams.size() > canvasPosition ? bitstreams.get(canvasPosition) : null; } catch (RuntimeException e) { throw new RuntimeException("The requested canvas is not available", e); } } - /** - * Attempts to find info.json file in the bitstream bundle and convert - * the json into the Info.class domain model for canvas and range parameters. - * @param context DSpace context - * @param bundleName the IIIF bundle - * @return info domain model - */ - public Info getInfo(Context context, Item item, String bundleName) { - Info info = null; - try { - ObjectMapper mapper = new ObjectMapper(); - // Look for expected json file bitstream in bundle. - Bitstream infoBitstream = bitstreamService - .getBitstreamByName(item, bundleName, "info.json"); - if (infoBitstream != null) { - InputStream is = bitstreamService.retrieve(context, infoBitstream); - info = mapper.readValue(is, Info.class); - } - } catch (IOException | SQLException e) { - log.warn("Unable to read info.json file.", e); - } catch (AuthorizeException e) { - log.warn("Not authorized to access info.json file.", e); - } - return info; - } - - /** - * Returns the range parameter List or null - * @param info the parameters model - * @return list of range models - */ - public List getRangesFromInfoObject(Info info) { - if (info != null) { - return info.getStructures(); - } - return null; - } - /** * Extracts canvas position from the URL input path. * @param canvasId e.g. "c12" @@ -219,72 +176,6 @@ public class IIIFUtils { return CANVAS_PATH_BASE + position; } - /** - * Convenience method to compare canvas parameter and bitstream list size. - * @param info the parameter model - * @param bitstreams the list of DSpace bitstreams - * @return true if sizes match - */ - public boolean isListSizeMatch(Info info, List bitstreams) { - // If Info is not null then the bitstream bundle contains info.json; exclude - // the file from comparison. - if (info != null && info.getCanvases().size() == bitstreams.size() - 1) { - return true; - } - return false; - } - - /** - * Convenience method verifies that the requested canvas exists in the - * parameters model object. - * @param info parameter model - * @param canvasPosition requested canvas position - * @return true if index is in bounds - */ - public boolean canvasOutOfBounds(Info info, int canvasPosition) { - return canvasPosition < 0 || canvasPosition >= info.getCanvases().size(); - } - - /** - * Validates info.json for a single canvas. - * Unless global settings are being used, when canvas information is not available - * use defaults. The canvas information is defined in the info.json file. - * @param info the information model - * @param position the position of the requested canvas - * @return information model - */ - public Info validateInfoForSingleCanvas(Info info, int position) { - if (info != null && info.getGlobalDefaults() != null) { - if (canvasOutOfBounds(info, position) && !info.getGlobalDefaults().isActivated()) { - log.warn("Canvas for position " + position + " not defined.\n" + - "Ignoring info.json canvas definitions and using defaults. " + - "Any other canvas-level annotations will also be ignored."); - info.setCanvases(new ArrayList<>()); - } - } - return info; - } - - /** - * Unless global settings are being used, when canvas information list size does - * not match the number of bitstreams use defaults. The canvas information is - * defined in the info.json file. - * @param info the information model - * @param bitstreams the list of bitstreams - * @return information model - */ - public Info validateInfoForManifest(Info info, List bitstreams) { - if (info != null && info.getGlobalDefaults() != null) { - if (!isListSizeMatch(info, bitstreams) && !info.getGlobalDefaults().isActivated()) { - log.warn("Mismatch between info.json canvases and DSpace bitstream count.\n" + - "Ignoring info.json canvas definitions and using defaults." + - "Any other canvas-level annotations will also be ignored."); - info.setCanvases(new ArrayList<>()); - } - } - return info; - } - /** * Serializes the json response. * @param resource to be serialized @@ -312,4 +203,73 @@ public class IIIFUtils { } return false; } + + public List getSeeAlsoBitstreams(Item item) { + return new ArrayList(); + } + + public String getIIIFLabel(DSpaceObject dso, String defaultLabel) { + return dso.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_LABEL)) + .findFirst().map(m -> m.getValue()).orElse(defaultLabel); + } + + public String getIIIFDescription(DSpaceObject dso, String defaultDescription) { + return dso.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_DESCRIPTION)) + .findFirst().map(m -> m.getValue()).orElse(defaultDescription); + } + + public List getIIIFToCs(DSpaceObject dso, String prefix) { + List tocs = dso.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_TOC)) + .map(m -> StringUtils.isNotBlank(prefix) ? prefix + TOC_SEPARATOR + m.getValue() : m.getValue()) + .collect(Collectors.toList()); + if (tocs.size() == 0 && StringUtils.isNotBlank(prefix)) { + return List.of(prefix); + } else { + return tocs; + } + } + + public String getIIIFFirstToC(DSpaceObject dso) { + return dso.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_TOC)) + .findFirst().map(m -> m.getValue()).orElse(null); + } + + public String getIIIFViewingHint(Item item, String defaultHint) { + return item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_VIEWING_HINT)) + .findFirst().map(m -> m.getValue()).orElse(defaultHint); + } + + public int getCanvasWidth(Bitstream bitstream, Bundle bundle, Item item, int defaultWidth) { + return getSizeFromMetadata(bitstream, METADATA_IMAGE_WIDTH, + getSizeFromMetadata(bundle, METADATA_IMAGE_WIDTH, + getSizeFromMetadata(item, METADATA_IMAGE_WIDTH, defaultWidth))); + } + + public int getCanvasHeight(Bitstream bitstream, Bundle bundle, Item item, int defaultHeight) { + return getSizeFromMetadata(bitstream, METADATA_IMAGE_HEIGTH, + getSizeFromMetadata(bundle, METADATA_IMAGE_HEIGTH, + getSizeFromMetadata(item, METADATA_IMAGE_HEIGTH, defaultHeight))); + } + + private int getSizeFromMetadata(DSpaceObject dso, String metadata, int defaultValue) { + return dso.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(metadata)) + .findFirst().map(m -> castToInt(m, defaultValue)).orElse(defaultValue); + } + + private int castToInt(MetadataValue m, int defaultWidth) { + try { + Integer.parseInt(m.getValue()); + } catch (NumberFormatException e) { + log.error("Error parsing " + m.getMetadataField().toString('.') + " of " + m.getDSpaceObject().getID() + + " the value " + m.getValue() + " is not an integer. Returning the default."); + } + return defaultWidth; + } + } 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 b6611b8d83..507615b352 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 @@ -13,6 +13,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.util.UUID; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; @@ -31,33 +32,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { public static final String IIIFBundle = "IIIF"; - /** - * info.json set for individual canvases. - */ - String info = "{\"globalDefaults\":{\"activated\": false,\"label\": \"\",\"width\": 0,\"height\": 0}," + - "\"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, \"height\": 4220, \"pos\": 0}]" + - ",\"structures\": []}"; - - /** - * info.json with structures and global canvas setting. - */ - String infoWithStructures = "{\"globalDefaults\":" + - "{\"activated\": true,\"label\": \"Global\",\"width\": 2000,\"height\": 3000}," + - "\"canvases\":[]," + - "\"structures\": " + - "[{\"label\":\"Section 1\",\"start\":1}," + - "{\"label\":\"Section 2\",\"start\":2}]" + - "}"; - - /** - * info.json defaulting to global canvas settings. - */ - String globalInfoConfig = "{\"globalDefaults\":{\"activated\": true,\"label\": \"Global\",\"width\": 2000," + - "\"height\": 3000}, \"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, " + - "\"height\": 4220, \"pos\": 0}],\"structures\": []}"; - @Test - public void missingBundleTest() throws Exception { + public void disabledTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -68,13 +44,28 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withEntityType("IIIFSearchable") .build(); - context.restoreAuthSystemState(); - // Status 500 - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().is(500)); + Item publicItem2 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 2") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .disableIIIF() + .build(); + + context.restoreAuthSystemState(); + // Status 404 + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().is(404)); + getClient().perform(get("/iiif/" + publicItem2.getID() + "/manifest")) + .andExpect(status().is(404)); + } + + @Test + public void notFoundTest() throws Exception { + // Status 404 + getClient().perform(get("/iiif/" + UUID.randomUUID().toString() + "/manifest")) + .andExpect(status().is(404)); } @Test @@ -89,7 +80,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withEntityType("IIIFSearchable") + .enableIIIF() + .enableIIIFSearch() .build(); String bitstreamContent = "ThisIsSomeDummyText"; @@ -97,8 +89,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { Bitstream bitstream2 = null; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { bitstream1 = BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") + .createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") .withMimeType("image/jpeg") .build(); } @@ -106,8 +98,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { bitstream2 = BitstreamBuilder .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream2") - .withMimeType("image/jpeg") + .withName("Bitstream2.png") + .withMimeType("image/png") .build(); } @@ -124,6 +116,13 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { 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(1200))) + .andExpect(jsonPath("$.sequences[0].canvases[0].images[0].resource.service.@id", + Matchers.endsWith(bitstream1.getID().toString()))) + .andExpect(jsonPath("$.sequences[0].canvases[1].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c1"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Page 2"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].images[0].resource.service.@id", + Matchers.endsWith(bitstream2.getID().toString()))) .andExpect(jsonPath("$.related.@id", Matchers.containsString("/items/" + publicItem1.getID()))); } @@ -140,6 +139,9 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") + .enableIIIF() + .withIIIFCanvasWidth(2000) + .withIIIFCanvasHeight(3000) .withEntityType("IIIFSearchable") .build(); @@ -147,19 +149,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") + .withName("Bitstream1.jpg") .withMimeType("image/jpeg") + .withIIIFLabel("Custom Label") + .withIIIFCanvasWidth(3163) + .withIIIFCanvasHeight(4220) + //.withMimeType("image/jpeg") .build(); } - - try (InputStream is = IOUtils.toInputStream(this.globalInfoConfig, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, "IIIF") - .withName("info.json") - .withMimeType("application/json") - .build(); - } - context.restoreAuthSystemState(); // Expect canvas label, width and height to match bitstream description. getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) @@ -185,23 +182,19 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withEntityType("IIIFSearchable") + .enableIIIF() + .enableIIIFSearch() .build(); String bitstreamContent = "ThisIsSomeText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - - try (InputStream is = IOUtils.toInputStream(this.info, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, "IIIF") - .withName("info.json") - .withMimeType("application/json") + .withName("Bitstream1.png") + .withMimeType("image/png") + .withIIIFLabel("Custom Label") + .withIIIFCanvasWidth(3163) + .withIIIFCanvasHeight(4220) .build(); } @@ -231,14 +224,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") .withMetadata("dc", "rights", "uri", "https://license.org") - .withEntityType("IIIF") + .enableIIIF() .build(); String bitstreamContent = "ThisIsSomeDummyText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") + .withName("Bitstream1.jpg") .withMimeType("image/jpeg") .build(); } @@ -247,8 +240,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { BitstreamBuilder. createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream2") - .withMimeType("image/jpeg") + .withName("Bitstream2.png") + .withMimeType("image/png") .build(); } @@ -256,8 +249,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { BitstreamBuilder. createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream3") - .withMimeType("image/jpeg") + .withName("Bitstream3.tiff") + .withMimeType("image/tiff") .build(); } @@ -292,37 +285,36 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withEntityType("IIIFSearchable") + .withIIIFCanvasHeight(3000) + .withIIIFCanvasWidth(2000) + .withIIIFCanvasLabel("Global") + .enableIIIF() + .enableIIIFSearch() .build(); String bitstreamContent = "ThisIsSomeText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") + .withName("Bitstream1.jpg") .withMimeType("image/jpeg") + .withIIIFToC("Section 1") .build(); } try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream2") - .withMimeType("image/jpeg") + .withName("Bitstream2.png") + .withMimeType("image/png") + .withIIIFToC("Section 2") .build(); } try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream3") - .withMimeType("image/jpeg") - .build(); - } - - try (InputStream is = IOUtils.toInputStream(this.infoWithStructures, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, "IIIF") - .withName("info.json") - .withMimeType("application/json") + .withName("Bitstream3.tiff") + .withMimeType("image/tiff") + .withIIIFToC("Section 2") .build(); } @@ -355,14 +347,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") .withMetadata("dc", "rights", "uri", "https://license.org") - .withEntityType("IIIF") + .enableIIIF() .build(); String bitstreamContent = "ThisIsSomeDummyText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") + createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") .withMimeType("image/jpeg") .build(); } @@ -393,14 +385,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") .withMetadata("dc", "rights", "uri", "https://license.org") - .withEntityType("IIIF") + .enableIIIF() .build(); String bitstreamContent = "ThisIsSomeDummyText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") + createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") .withMimeType("image/jpeg") .build(); } @@ -439,21 +431,21 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withEntityType("IIIF") + .enableIIIF() .build(); String bitstreamContent = "ThisIsSomeDummyText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. createBitstream(context, publicItem1, is) - .withName("Bitstream1") + .withName("Bitstream1.jpg") .withMimeType("image/jpeg") .build(); } try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. createBitstream(context, publicItem1, is) - .withName("Bitstream1") + .withName("Bitstream2.pdf") .withMimeType("application/pdf") .build(); } @@ -484,14 +476,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withEntityType("IIIF") + .enableIIIF() .build(); String bitstreamContent = "ThisIsSomeDummyText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("IMG1") + createBitstream(context, publicItem1, is) + .withName("IMG1.jpg") .withMimeType("image/jpeg") .build(); } @@ -516,21 +508,21 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withEntityType("IIIF") + .enableIIIF() .build(); String bitstreamContent = "ThisIsSomeDummyText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("IMG1") + createBitstream(context, publicItem1, is) + .withName("IMG1.jpg") .withMimeType("image/jpeg") .build(); } context.restoreAuthSystemState(); - // Status 500. The item contains only one bitstream. The item manifest likewise contains one canvas. + // Status 404. The item contains only one bitstream. The item manifest likewise contains one canvas. getClient().perform(get("/iiif/" + publicItem1.getID() + "/canvas/c2")) - .andExpect(status().is(500)); + .andExpect(status().is(404)); } @Test @@ -545,14 +537,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withTitle("Public item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withEntityType("IIIF") + .enableIIIF() .build(); String bitstreamContent = "ThisIsSomeDummyText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("IMG1") + createBitstream(context, publicItem1, is) + .withName("IMG1.jpg") .withMimeType("image/jpeg") .build(); } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 7378bfcbb4..9c5526e4fa 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -818,7 +818,7 @@ registry.metadata.load = schema-periodical-types.xml registry.metadata.load = schema-publicationIssue-types.xml registry.metadata.load = schema-publicationVolume-types.xml registry.metadata.load = dspace-types.xml - +registry.metadata.load = iiif-types.xml #---------------------------------------------------------------# diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index 5f30b672e2..3fad86f2c7 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -11,8 +11,51 @@ iiif.cors.allowed-origins = ${dspace.ui.url} # (Requires reboot of servlet container, e.g. Tomcat, to reload) iiif.cors.allow-credentials = false +# metadata to include at the resource level in the manifest +# labels are set in the Messages.properties i18n file +iiif.metadata.item = dc.title +iiif.metadata.item = dc.date.issued +iiif.metadata.item = dc.contributor.* +iiif.metadata.item = dc.description.abstract +# metadata to be added to the canvas from the bitstream, labels are set in +the Messages.properties i18n file. It is possible to include the technical +# information stored in the bitstream format registry and in the checksum using the placeholder +# @format@ to include the short description of the bitstream format as entered by the user or +# stored in the registry +# @mimemtype@ to include the mimetype associated with the bitstream format in the registry +# @checksum@ to include the computed checksum for the file and the used algorithm +# @bytes@ to include the size of the images in bytes +iiif.metadata.bitstream = dc.title +iiif.metadata.bitstream = dc.description +iiif.metadata.bitstream = @format@ +iiif.metadata.bitstream = @mimetype@ +iiif.metadata.bitstream = iiif.image.width +iiif.metadata.bitstream = iiif.image.height +iiif.metadata.bitstream = @bytes@ +iiif.metadata.bitstream = @checksum@ +# the metadata to use to provide machine readable information about the resource right usage +iiif.license.uri = dc.rights.uri +# static text to be used as attribution in the iiif manifests +iiif.attribution = ${dspace.name} +iiif.logo.image = ${dspace.ui.url}/assets/images/dspace-logo.svg +# (optional) one of individuals, paged or continuous. Can be overridden at the item level via +# the iiif.view.hint metadata +iiif.document.viewing.hint = +# ???? +iiif.url = + +iiif.image.server = + +iiif.solr.search.url = +# ???? +iiif.bitstream.url = + +# default value for the canvas size. Can be overridden at the item, bundle or bitstream level +# via the iiif.image.width e iiif.image.height metadata +# iiif.canvas.default-width = 1200 +# iiif.canvas.default-heigth = 1600 \ No newline at end of file diff --git a/dspace/config/registries/dspace-types.xml b/dspace/config/registries/dspace-types.xml index af783e0009..ab0c1bc40f 100644 --- a/dspace/config/registries/dspace-types.xml +++ b/dspace/config/registries/dspace-types.xml @@ -37,4 +37,10 @@ Stores the type of Entity that a specific Item represents + + dspace + iiif + enabled + Stores a boolean text value (true or false) to indicate if the iiif feature is enabled or not for the dspace object. If absent the value is derived from the parent dspace object + diff --git a/dspace/config/registries/iiif-types.xml b/dspace/config/registries/iiif-types.xml new file mode 100644 index 0000000000..3a30fe596e --- /dev/null +++ b/dspace/config/registries/iiif-types.xml @@ -0,0 +1,66 @@ + + + + DSpace IIIF Schema + Andrea Bollini + + + + iiif + http://dspace.org/iiif + + + + iiif + label + Metadata field used to set the IIIF label associated with the resource otherwise the system will derive one according to the configuration and metadata + + + + iiif + description + Metadata field used to set the IIIF description associated with the resource + + + + iiif + toc + Metadata field used to set the position of the iiif resource in the structure. Levels are separated by triple pipe ||| can be applied to Bundles and Bitstreams + + + + iiif + canvas + label + Metadata field used to set the base label used to name all the canvas in the Item. The canvas label will be generated using the value of this metadata as prefix and the canvas position + + + + iiif + viewing + hint + Metadata field used to set the viewing hint overriding the configuration value if any + + + + iiif + image + width + Metadata field used to store the width of an image in px + + + + iiif + image + height + Metadata field used to store the height of an image in px + + + + iiif + search + enabled + Metadata field used to enable the IIIF Search service at the item level + + + From 82ff056681e22fd8e6b7d06a29a6f098219578a5 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 22 Sep 2021 18:07:23 +0200 Subject: [PATCH 0305/1254] implemented endpoint that is able to retrieve supported entity types provided an external provider and tests --- .../dspace/content/EntityTypeServiceImpl.java | 12 ++ .../org/dspace/content/dao/EntityTypeDAO.java | 22 ++++ .../content/dao/impl/EntityTypeDAOImpl.java | 27 ++++ .../content/service/EntityTypeService.java | 21 +++ .../app/rest/model/ExternalSourceRest.java | 9 ++ ...xternalSourceEntityTypeLinkRepository.java | 73 +++++++++++ .../rest/ExternalSourcesRestControllerIT.java | 124 +++++++++++++++++- 7 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java diff --git a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java index f3e156dd42..2790c36924 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java @@ -156,4 +156,16 @@ public class EntityTypeServiceImpl implements EntityTypeService { } return types; } + + @Override + public List getEntityTypesByNames(Context context, List names, Integer limit, Integer offset) + throws SQLException { + return entityTypeDAO.getEntityTypesByNames(context, names, limit, offset); + } + + @Override + public int countEntityTypesByNames(Context context, List names) throws SQLException { + return entityTypeDAO.countEntityTypesByNames(context, names); + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/EntityTypeDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/EntityTypeDAO.java index 4e8a5934dd..8dbdc0ef70 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/EntityTypeDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/EntityTypeDAO.java @@ -8,6 +8,7 @@ package org.dspace.content.dao; import java.sql.SQLException; +import java.util.List; import org.dspace.content.EntityType; import org.dspace.core.Context; @@ -32,4 +33,25 @@ public interface EntityTypeDAO extends GenericDAO { */ public EntityType findByEntityType(Context context, String entityType) throws SQLException; + /** + * + * @param context DSpace context object + * @param names List of Entity type names that you want to retrieve + * @param limit paging limit + * @param offset the position of the first result to return + * @return + * @throws SQLException if database error + */ + public List getEntityTypesByNames(Context context, List names, Integer limit, Integer offset) + throws SQLException; + + /** + * + * @param context DSpace context object + * @param names List of Entity type names that you want to retrieve + * @return + * @throws SQLException If database error + */ + public int countEntityTypesByNames(Context context, List names) throws SQLException; + } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java index f7a4dcfdf3..489f4cd066 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java @@ -8,8 +8,11 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Root; import org.dspace.content.EntityType; @@ -39,4 +42,28 @@ public class EntityTypeDAOImpl extends AbstractHibernateDAO implemen entityType.toUpperCase())); return uniqueResult(context, criteriaQuery, true, EntityType.class); } + + @Override + public List getEntityTypesByNames(Context context, List names, Integer limit, Integer offset) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, EntityType.class); + Root entityTypeRoot = criteriaQuery.from(EntityType.class); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.desc(entityTypeRoot.get(EntityType_.label))); + criteriaQuery.select(entityTypeRoot).orderBy(orderList); + criteriaQuery.where(entityTypeRoot.get(EntityType_.LABEL).in(names)); + return list(context, criteriaQuery, false, EntityType.class, limit, offset); + } + + @Override + public int countEntityTypesByNames(Context context, List names) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, EntityType.class); + Root entityTypeRoot = criteriaQuery.from(EntityType.class); + criteriaQuery.select(entityTypeRoot); + criteriaQuery.where(entityTypeRoot.get(EntityType_.LABEL).in(names)); + return count(context, criteriaQuery, criteriaBuilder, entityTypeRoot); + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java index b06bc34c50..38b905fb74 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java @@ -61,4 +61,25 @@ public interface EntityTypeService extends DSpaceCRUDService { public List getSubmitAuthorizedTypes(Context context) throws SQLException, SolrServerException, IOException; + /** + * + * @param context DSpace context object + * @param names List of Entity type names that you want to retrieve + * @param limit paging limit + * @param offset the position of the first result to return + * @return + * @throws SQLException if database error + */ + public List getEntityTypesByNames(Context context, List names,Integer limit, Integer offset) + throws SQLException; + + /** + * + * @param context DSpace context object + * @param names List of Entity type names that you want to retrieve + * @return + * @throws SQLException if database error + */ + public int countEntityTypesByNames(Context context, List names) throws SQLException; + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java index f1f7dc7825..639c2cf72e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceRest.java @@ -12,11 +12,20 @@ import org.dspace.app.rest.RestResourceController; /** * This class serves as a REST representation for an External Source */ +@LinksRest(links = { + @LinkRest( + name = ExternalSourceRest.ENTITY_TYPES, + method = "getSupportedEntityTypes" + ) +}) public class ExternalSourceRest extends BaseObjectRest { + private static final long serialVersionUID = 6951711935287912124L; + public static final String NAME = "externalsource"; public static final String PLURAL_NAME = "externalsources"; public static final String CATEGORY = RestAddressableModel.INTEGRATION; + public static final String ENTITY_TYPES = "entityTypes"; @Override public String getCategory() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.java new file mode 100644 index 0000000000..4ed39c1893 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceEntityTypeLinkRepository.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.repository; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.app.rest.model.EntityTypeRest; +import org.dspace.app.rest.model.ExternalSourceRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.EntityType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.core.Context; +import org.dspace.external.provider.AbstractExternalDataProvider; +import org.dspace.external.service.ExternalDataService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.stereotype.Component; + +/** + * Link repository for "EntityTypes" supported of an individual ExternalDataProvider. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(ExternalSourceRest.CATEGORY + "." + ExternalSourceRest.NAME + "." + ExternalSourceRest.ENTITY_TYPES) +public class ExternalSourceEntityTypeLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private ExternalDataService externalDataService; + + public Page getSupportedEntityTypes(@Nullable HttpServletRequest request, + String externalSourceName, + @Nullable Pageable pageable, + Projection projection) { + Context context = ContextUtil.obtainContext(request); + List entityTypes = Collections.emptyList(); + AbstractExternalDataProvider externalDataProvider = (AbstractExternalDataProvider) + externalDataService.getExternalDataProvider(externalSourceName); + if (Objects.isNull(externalDataProvider)) { + throw new ResourceNotFoundException("No such ExternalDataProvider: " + externalSourceName); + } + int total = 0; + List supportedEntityTypes = externalDataProvider.getSupportedEntityTypes(); + try { + if (CollectionUtils.isNotEmpty(supportedEntityTypes)) { + entityTypes = entityTypeService.getEntityTypesByNames(context, supportedEntityTypes, + Math.toIntExact(pageable.getPageSize()), + Math.toIntExact(pageable.getOffset())); + total = entityTypeService.countEntityTypesByNames(context, supportedEntityTypes); + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return converter.toRestPage(entityTypes, pageable, total, utils.obtainProjection()); + } + +} \ No newline at end of file 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 484171fcb6..1a9db10df1 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 @@ -6,20 +6,32 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest; - +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; 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.Arrays; + +import org.dspace.app.rest.matcher.EntityTypeMatcher; import org.dspace.app.rest.matcher.ExternalSourceEntryMatcher; import org.dspace.app.rest.matcher.ExternalSourceMatcher; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.content.EntityType; +import org.dspace.external.provider.AbstractExternalDataProvider; +import org.dspace.external.service.ExternalDataService; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrationTest { + @Autowired + private ExternalDataService externalDataService; + @Test public void findAllExternalSources() throws Exception { getClient().perform(get("/api/integration/externalsources")) @@ -182,4 +194,114 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); } + @Test + public void findSupportedEntityTypesOfAnExternalDataProviderTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + EntityType orgUnit = EntityTypeBuilder.createEntityTypeBuilder(context, "OrgUnit").build(); + EntityType project = EntityTypeBuilder.createEntityTypeBuilder(context, "Project").build(); + EntityType funding = EntityTypeBuilder.createEntityTypeBuilder(context, "Funding").build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + try { + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("mock")) + .setSupportedEntityTypes(Arrays.asList("Publication", "OrgUnit")); + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("pubmed")) + .setSupportedEntityTypes(Arrays.asList("Project","Publication", "Funding")); + + getClient(tokenAdmin).perform(get("/api/integration/externalsources/mock/entityTypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entityTypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(publication), + EntityTypeMatcher.matchEntityTypeEntry(orgUnit) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))); + + getClient(tokenAdmin).perform(get("/api/integration/externalsources/pubmed/entityTypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entityTypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(project), + EntityTypeMatcher.matchEntityTypeEntry(publication), + EntityTypeMatcher.matchEntityTypeEntry(funding) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); + } finally { + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("mock")) + .setSupportedEntityTypes(null); + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("pubmed")) + .setSupportedEntityTypes(null); + } + } + + @Test + public void findSupportedEntityTypesOfAnExternalDataProviderNotFoundTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/externalsources/WrongProvider/entityTypes")) + .andExpect(status().isNotFound()); + } + + @Test + public void findSupportedEntityTypesOfAnExternalDataProviderEmptyResponseTest() throws Exception { + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("mock")) + .setSupportedEntityTypes(null); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/externalsources/mock/entityTypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entityTypes").isEmpty()) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); + } + + @Test + public void findSupportedEntityTypesOfAnExternalDataProviderPaginationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + EntityType orgUnit = EntityTypeBuilder.createEntityTypeBuilder(context, "OrgUnit").build(); + EntityType project = EntityTypeBuilder.createEntityTypeBuilder(context, "Project").build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + try { + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("mock")) + .setSupportedEntityTypes(Arrays.asList("Publication", "OrgUnit", "Project")); + + getClient(tokenAdmin).perform(get("/api/integration/externalsources/mock/entityTypes") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entityTypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(publication), + EntityTypeMatcher.matchEntityTypeEntry(project) + ))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(2))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); + + getClient(tokenAdmin).perform(get("/api/integration/externalsources/mock/entityTypes") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entityTypes", contains( + EntityTypeMatcher.matchEntityTypeEntry(orgUnit) + ))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(2))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); + + } finally { + ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("mock")) + .setSupportedEntityTypes(null); + } + } + } From 84315b93a09c446e1391c91d2abde517facdfb58 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 22 Sep 2021 18:09:39 +0200 Subject: [PATCH 0306/1254] assigne collection entity type to item during submission proccess --- .../content/WorkspaceItemServiceImpl.java | 17 +++++++++++++++++ .../main/java/org/dspace/core/Constants.java | 6 ++++++ .../indexobject/CollectionIndexFactoryImpl.java | 11 +++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) 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 7675d298d6..cc5dcd8fd1 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -12,6 +12,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReaderException; @@ -108,6 +109,22 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { .addPolicy(context, item, Constants.DELETE, item.getSubmitter(), ResourcePolicy.TYPE_SUBMISSION); + Optional optionalType = collection.getMetadata() + .stream() + .filter(x -> x.getMetadataField().toString('.') + .equalsIgnoreCase("dspace.entity.type")) + .findFirst(); + + if (optionalType.isPresent()) { + MetadataValue original = optionalType.get(); + MetadataField metadataField = original.getMetadataField(); + MetadataSchema metadataSchema = metadataField.getMetadataSchema(); + itemService.addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), + metadataField.getQualifier(), original.getLanguage(), original.getValue()); + } else { + itemService.addMetadata(context, item, "dspace", "entity", "type", null, Constants.UNSET_ENTITY_TYPE); + } + // Copy template if appropriate Item templateItem = collection.getTemplateItem(); diff --git a/dspace-api/src/main/java/org/dspace/core/Constants.java b/dspace-api/src/main/java/org/dspace/core/Constants.java index 6dcb7d9df2..4fae3af642 100644 --- a/dspace-api/src/main/java/org/dspace/core/Constants.java +++ b/dspace-api/src/main/java/org/dspace/core/Constants.java @@ -226,6 +226,12 @@ public class Constants { public static final String DEFAULT_ENCODING = "UTF-8"; public static final String VIRTUAL_AUTHORITY_PREFIX = "virtual::"; + + /* + * Label used by the special entity type assigned when no explicit assignment is defined + */ + public static final String UNSET_ENTITY_TYPE = "unset"; + /** * Default constructor */ diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java index f1bbcb8994..57c1ffc4e5 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.apache.commons.lang3.StringUtils; import org.apache.solr.common.SolrInputDocument; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -23,6 +24,7 @@ import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.SearchUtils; import org.dspace.discovery.configuration.DiscoveryConfiguration; @@ -111,7 +113,7 @@ public class CollectionIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl toIgnoreMetadataFields = SearchUtils.getIgnoredMetadataFields(collection.getType()); addContainerMetadataField(doc, highlightedMetadataFields, toIgnoreMetadataFields, "dc.description", @@ -126,7 +128,12 @@ public class CollectionIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl Date: Wed, 22 Sep 2021 19:24:54 +0100 Subject: [PATCH 0307/1254] adding openaire funding unit tests --- .../impl/OpenAIREFundingDataProvider.java | 15 + .../config/spring/api/external-openaire.xml | 18 + .../external/MockOpenAIRERestConnector.java | 63 ++ .../impl/OpenAIREFundingDataProviderTest.java | 106 +++ .../dspace/external/openaire-no-projects.xml | 16 + .../org/dspace/external/openaire-project.xml | 74 +++ .../org/dspace/external/openaire-projects.xml | 610 ++++++++++++++++++ 7 files changed, 902 insertions(+) create mode 100644 dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml create mode 100644 dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java create mode 100644 dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java create mode 100644 dspace-api/src/test/resources/org/dspace/external/openaire-no-projects.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/openaire-project.xml create mode 100644 dspace-api/src/test/resources/org/dspace/external/openaire-projects.xml diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java index 64449de24a..76cc9753b5 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java @@ -16,6 +16,7 @@ import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; import eu.openaire.jaxb.helper.FundingHelper; @@ -85,6 +86,10 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { // we use base64 encoding in order to use slashes / and other // characters that must be escaped for the <:entry-id> String decodedId = new String(Base64.getDecoder().decode(id)); + if (!isValidProjectURI(decodedId)) { + log.error("Invalid ID for OpenAIREFunding - " + id); + return Optional.empty(); + } Response response = searchByProjectURI(decodedId); try { @@ -197,6 +202,16 @@ public class OpenAIREFundingDataProvider implements ExternalDataProvider { return connector.searchProjectByIDAndFunder(splittedURI[3], splittedURI[1], 1, 1); } + /** + * Validates if the project has the correct format + * + * @param projectURI + * @return true if the URI is valid + */ + private static boolean isValidProjectURI(String projectURI) { + return Pattern.matches(PREFIX + "/.+/.+/.*", projectURI); + } + /** * This method returns an URI based on OpenAIRE 3.0 guidelines * https://guidelines.openaire.eu/en/latest/literature/field_projectid.html that diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml new file mode 100644 index 0000000000..6d138f1fa4 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java b/dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.java new file mode 100644 index 0000000000..bac80e196c --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/external/MockOpenAIRERestConnector.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.external; + +import java.io.InputStream; +import javax.xml.bind.JAXBException; + +import eu.openaire.jaxb.helper.OpenAIREHandler; +import eu.openaire.jaxb.model.Response; + +/** + * Mock the OpenAIRE rest connector for unit testing
    + * will be resolved against static test xml files + * + * @author pgraca + * + */ +public class MockOpenAIRERestConnector extends OpenAIRERestConnector { + + public MockOpenAIRERestConnector(String url) { + super(url); + } + + @Override + public Response searchProjectByKeywords(int page, int size, String... keywords) { + try { + return OpenAIREHandler.unmarshal(this.getClass().getResourceAsStream("openaire-projects.xml")); + } catch (JAXBException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public Response searchProjectByIDAndFunder(String projectID, String projectFunder, int page, int size) { + try { + return OpenAIREHandler.unmarshal(this.getClass().getResourceAsStream("openaire-project.xml")); + } catch (JAXBException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public Response search(String path, int page, int size) { + try { + return OpenAIREHandler.unmarshal(this.getClass().getResourceAsStream("openaire-no-projects.xml")); + } catch (JAXBException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public InputStream get(String file, String accessToken) { + return this.getClass().getResourceAsStream("openaire-no-projects.xml"); + } +} diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java new file mode 100644 index 0000000000..c5a3b402bd --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java @@ -0,0 +1,106 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.external.provider.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.Optional; + +import org.dspace.AbstractDSpaceTest; +import org.dspace.external.MockOpenAIRERestConnector; +import org.dspace.external.model.ExternalDataObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Unit tests for OpenAIREFundingDataProvider + * + * @author pgraca + * + */ +@RunWith(MockitoJUnitRunner.class) +public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { + + @InjectMocks + OpenAIREFundingDataProvider openAIREFundingDataProvider; + + /** + * This method will be run before every test as per @Before. It will initialize + * resources required for each individual unit test. + * + * Other methods can be annotated with @Before here or in subclasses but no + * execution order is guaranteed + */ + @Before + public void init() { + openAIREFundingDataProvider.setSourceIdentifier("openAIREFunding"); + openAIREFundingDataProvider.setConnector(new MockOpenAIRERestConnector("https://api.openaire.eu")); + } + + @Test + public void testNumberOfResultsWSingleKeyword() { + assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + assertEquals("openAIREFunding.numberOfResults.query:mock", 77, + openAIREFundingDataProvider.getNumberOfResults("mock")); + } + + @Test + public void testNumberOfResultsWKeywords() { + assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + assertEquals("openAIREFunding.numberOfResults.query:mock+test", 77, + openAIREFundingDataProvider.getNumberOfResults("mock+test")); + } + + @Test + public void testQueryResultsWSingleKeyword() { + assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + List results = openAIREFundingDataProvider.searchExternalDataObjects("mock", 0, 10); + assertEquals("openAIREFunding.searchExternalDataObjects.size", 10, results.size()); + } + + @Test + public void testQueryResultsWKeywords() { + String value = "Mushroom Robo-Pic - Development of an autonomous robotic mushroom picking system"; + + assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + List results = openAIREFundingDataProvider.searchExternalDataObjects("mock+test", 0, 10); + assertEquals("openAIREFunding.searchExternalDataObjects.size", 10, results.size()); + assertTrue("openAIREFunding.searchExternalDataObjects.first.value", value.equals(results.get(0).getValue())); + } + + @Test + public void testGetDataObject() { + String id = "aW5mbzpldS1yZXBvL2dyYW50QWdyZWVtZW50L0ZDVC81ODc2LVBQQ0RUSS8xMTAwNjIvUFQ="; + String value = "Portuguese Wild Mushrooms: Chemical characterization and functional study" + + " of antiproliferative and proapoptotic properties in cancer cell lines"; + + assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + + Optional result = openAIREFundingDataProvider.getExternalDataObject(id); + + assertTrue("openAIREFunding.getExternalDataObject.exists", result.isPresent()); + assertTrue("openAIREFunding.getExternalDataObject.value", value.equals(result.get().getValue())); + } + + @Test + public void testGetDataObjectWInvalidId() { + String id = "WRONGID"; + + assertNotNull("openAIREFundingDataProvider is not null", openAIREFundingDataProvider); + + Optional result = openAIREFundingDataProvider.getExternalDataObject(id); + + assertTrue("openAIREFunding.getExternalDataObject.notExists:WRONGID", result.isEmpty()); + } +} diff --git a/dspace-api/src/test/resources/org/dspace/external/openaire-no-projects.xml b/dspace-api/src/test/resources/org/dspace/external/openaire-no-projects.xml new file mode 100644 index 0000000000..481f2e14ec --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/openaire-no-projects.xml @@ -0,0 +1,16 @@ + + +
    + (oaftype exact project) and ( mushroomss) + en_US + 10 + 1 + 0 + + +
    + + + + +
    \ No newline at end of file diff --git a/dspace-api/src/test/resources/org/dspace/external/openaire-project.xml b/dspace-api/src/test/resources/org/dspace/external/openaire-project.xml new file mode 100644 index 0000000000..2b7c489442 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/openaire-project.xml @@ -0,0 +1,74 @@ + + +
    + (oaftype exact project) and (projectcode_nt exact "110062") and (fundershortname exact "FCT") + en_US + 10 + 1 + 1 + + +
    + + +
    + fct_________::59523e9f4736c2cab70a470f088b53dd + 2021-08-06 + 2021-08-06 +
    + + + + + fct_________::110062 + http://www.fct.pt/apoios/projectos/consulta/vglobal_projecto.phtml.en?idProjecto=110062&idElemConcurso=3734 + 110062 + PTDC/AGR-ALI/110062/2009 + Portuguese Wild Mushrooms: Chemical characterization and functional study of antiproliferative and proapoptotic properties in cancer cell lines + 2010-12-24 + 2013-12-23 + PTDC/2009 + Agricultural and Forestry Sciences - Food Science and Technology + 0 + false + false + false + 0.0 + 0.0 + + + fct_________::FCT + FCT + Fundao para a Cincia e a Tecnologia, I.P. + PT + + + fct_________::FCT::5876-PPCDTI + 5876-PPCDTI + 5876-PPCDTI + + fct:program + + + + false + false + 0.900 + null + + + + + + + +
    +
    + + +
    \ No newline at end of file diff --git a/dspace-api/src/test/resources/org/dspace/external/openaire-projects.xml b/dspace-api/src/test/resources/org/dspace/external/openaire-projects.xml new file mode 100644 index 0000000000..225055eb92 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/external/openaire-projects.xml @@ -0,0 +1,610 @@ + + +
    + (oaftype exact project) and ( mushroom) + en_US + 10 + 1 + 77 + + +
    + + +
    + rcuk________::98b50e6e0715fad40627833c7030d3c3 + 2018-02-06 + 2021-02-20 +
    + + + + + rcuk________::103679 + 103679 + Mushroom Robo-Pic - Development of an autonomous robotic mushroom picking system + 2017-10-01 + 2019-06-30 + 0 + false + false + false + 0.0 + 0.0 + + + rcuk________::RCUK + UKRI + UK Research and Innovation + GB + + + rcuk________::RCUK::Innovate UK + Innovate UK + Innovate UK + + rcuk:fundingStream + + + + false + false + 0.900 + null + + + + + rcuk________::ebafcf5f45afa2e9807f981e668db66b + Littleport Mushroom Farms Llp + + + + + + + +
    + +
    + rcuk________::6ac77c83ee0d98c433f91f3dc83074b2 + 2017-11-04 + 2021-02-20 +
    + + + + + rcuk________::752540 + 752540 + Exending Shelf Life of Mushroom Growing Kits + 2015-05-01 + 2015-10-31 + 0 + false + false + false + 0.0 + 0.0 + + + rcuk________::RCUK + UKRI + UK Research and Innovation + GB + + + rcuk________::RCUK::Innovate UK + Innovate UK + Innovate UK + + rcuk:fundingStream + + + + false + false + 0.900 + null + + + + + rcuk________::f2a533e22408279c50f647779633cf69 + Espresso Mushroom Company Ltd + + + + + + + +
    + +
    + arc_________::e1da9b244237847b24379fb1b11fb151 + 2015-08-24 + 2018-11-20 +
    + + + + arc_________::LP0220040 + http://purl.org/au-research/grants/arc/LP0220040 + LP0220040 + Use of Organic Residues in Edible Mushroom Production + 2002-01-01 + 2003-12-31 + compost,exotic mushrooms,mushroom production,organic wastes,peat,wood processing wastes + 0 + false + false + false + 0.0 + 0.0 + + + arc_________::ARC + ARC + Australian Research Council (ARC) + AU + + + arc_________::ARC::Linkage Projects + Linkage Projects + Linkage Projects + + arc:fundingStream + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + rcuk________::c137d9bfad46b1ebcc9b3f06e6eb5683 + 2018-08-01 + 2021-02-20 +
    + + + + + rcuk________::133611 + 133611 + The development of a mushroom harvesting machine to increase yield and production while reducing waste and labour shortage risk + 2018-07-01 + 2019-06-30 + 0 + false + false + false + 0.0 + 0.0 + + + rcuk________::RCUK + UKRI + UK Research and Innovation + GB + + + rcuk________::RCUK::Innovate UK + Innovate UK + Innovate UK + + rcuk:fundingStream + + + + false + false + 0.900 + null + + + + + rcuk________::560dfc3b58d1ab0d8a8957a943a76962 + + Mushroom Machine Company Limited + + + + + + +
    + +
    + corda__h2020::c97f7d6f1ff338991c0ec20b33ddb1e0 + 2018-07-21 + 2021-07-19 +
    + + + + + corda__h2020::820352 + 820352 + Smartmushroom + Smart MAnagement of spent mushRoom subsTrate to lead the MUSHROOM sector towards a circular economy + 2018-08-01 + 2021-01-31 + H2020-EIC-FTI-2018-2020 + 0 + false + Fast Track to Innovation (FTI) + true + false + Waste from animal breeding and agriculture, specifically horse and chicken manure and wheat straw, are the raw materials of the growing substrate of mushroom. To grow 1 tonne of mushroom, 3 to 4 tonnes of substrate are needed. However, when mushroom production is completed the substrate cannot be used for another growing cycle due to the depletion of nutrients needed for mushroom growing and it is called Spent Mushroom Substrate (SMS) and becomes a waste that should be managed according to regulations. In Europe, c.a. 3.65 million tons of SMS are generated each year. SMS is a high-moisture content bulk material rich in organic matter and nutrients and it could be reused in agriculture by adding it to the soils as amendment or mulch or weathered to be reused as casing soil. However, nitrates directive set a disposal limit that makes that large quantities of SMS cannot be simply spread in soils next to growers’ facilities, as there is a high risk of leachates and water pollution. Due to its low bulk density and high water content, transportation costs are high and therefore storage is becoming a sound problem. SmartMUSHROOM aims to increase mushroom growers’ waste management efficiency by using a new technology which allow them to obtain enough biogas from fresh SMS to dry a mixture of digestate and additional fresh SMS and pelletize it targeting to obtain a marketable high-quality organic fertilizer rich in organic matter and in nutrients, easy to handle, store and transport to any farming region in Europe. A perfect example of biobased circular economy. The aim of the project is to build a pilot plant to demonstrate the technology and find the best commercial formulation for the pellets to enter organic farming market. After the end of project we aim to build at least 18 treatment plants that will place in market 153,000 tonnes of SMS-pellets, generating a total Turnover of 54M€ in the period 2021-2025 and up to 105 related new jobs. + EUR + 2977940.0 + 2264140.0 + + + ec__________::EC + EC + European Commission + EU + + + ec__________::EC::H2020::IA + Innovation action + IA + ec:h2020toas + + + ec__________::EC::H2020 + H2020 + Horizon 2020 Framework Programme + + ec:h2020fundings + + + + + + false + false + 0.900 + null + + + + + pending_org_::39d5641e14f7cfe56e3e836199e33eba + ECOBELIEVE DOO + + ECOBELIEVE DOO GRKINJA + + + pending_org_::3c128a988f1404fed53e1cd8a61df51b + Asociacion Profesional de Productores de Compost y Hongos, de la Rioja, Navarra y Aragón + + ASOCHAMP + + + pending_org_::7555b3c59a033e789ce6190a6c9f39aa + INVESTIGACION Y DESARROLLO CASTILLA Y LEON S.A + + IDECAL S.A. + + + pending_org_::4193497542c7b30e3f50f52414905579 + NOVIS GMBH + + NOVIS GMBH + + + + + + +
    + +
    + nwo_________::c091653b5930a5a11c748720167b04d7 + 2016-06-23 + 2018-08-07 +
    + + + + + nwo_________::2300148817 + 2300148817 + Production of therapeutic proteins in mushroom + 2007-09-01 + 2012-04-30 + 0 + false + false + false + 0.0 + 0.0 + + + nwo_________::NWO + NWO + Netherlands Organisation for Scientific Research (NWO) + NL + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + nwo_________::5ee5a31d8c77215faef3bd35bd9696ff + 2016-06-23 + 2018-08-07 +
    + + + + + nwo_________::2300148209 + 2300148209 + Control of Verticillium fungicola on mushroom + 2006-10-01 + 2012-08-10 + 0 + false + false + false + 0.0 + 0.0 + + + nwo_________::NWO + NWO + Netherlands Organisation for Scientific Research (NWO) + NL + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + nwo_________::98df9c76cbd6537553284af850398659 + 2016-06-23 + 2018-08-07 +
    + + + + + nwo_________::2300147728 + 2300147728 + Master switches of initiation of mushroom formation + 2005-11-01 + 2012-09-12 + 0 + false + false + false + 0.0 + 0.0 + + + nwo_________::NWO + NWO + Netherlands Organisation for Scientific Research (NWO) + NL + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + nwo_________::7ddc7f2f259735f312a27c54f6b2ee5d + 2016-06-23 + 2018-08-07 +
    + + + + + nwo_________::2300164658 + 2300164658 + Push the white button; controlling mushroom formation + 2011-09-01 + 0 + false + false + false + 0.0 + 0.0 + + + nwo_________::NWO + NWO + Netherlands Organisation for Scientific Research (NWO) + NL + + + + false + false + 0.900 + null + + + + + + + +
    + +
    + nsf_________::c5fa29db776dc4f21919e12cbaea37eb + 2016-03-11 + 2018-08-07 +
    + + + + + nsf_________::6112554 + 6112554 + Respiratory Mechanisms in Cultivated Mushroom + 1961-01-01 + 1963-01-01 + 0 + false + false + false + 0.0 + 0.0 + + + nsf_________::NSF + NSF + National Science Foundation + US + + + + false + false + 0.900 + null + + + + + openorgs____::2f4b2e4dcb319a5f66e887d2fd555734 + + University of Delaware + UD + + + + + + +
    + +
    + + +
    \ No newline at end of file From 1f552683a7b1c23f2ef63c7e12f9134eaad2dff6 Mon Sep 17 00:00:00 2001 From: Nathan Buckingham Date: Wed, 22 Sep 2021 14:57:24 -0400 Subject: [PATCH 0308/1254] Remove unecessary deletion --- .../src/test/java/org/dspace/app/packager/PackagerIT.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index d7bdce9279..ddc2b115d8 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -87,14 +87,7 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { @After public void destroy() throws SQLException, IOException { - context.turnOffAuthorisationSystem(); - CommunityBuilder.deleteCommunity(parentCommunity.getID()); - CommunityBuilder.deleteCommunity(child1.getID()); - CommunityBuilder.deleteCommunity(col1.getID()); - CommunityBuilder.deleteCommunity(article.getID()); - CommunityBuilder.deleteCommunity(article.getID()); tempFile.delete(); - context.restoreAuthSystemState(); } @Test From ecd6510e2198fd3ef60bbf1b3ec38f06e303e590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Wed, 22 Sep 2021 20:16:11 +0100 Subject: [PATCH 0309/1254] changing mock usage externalservice --- .../config/spring/api/external-openaire.xml | 4 ++-- .../impl/OpenAIREFundingDataProviderTest.java | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index 6d138f1fa4..d3a7223491 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true"> - + @@ -12,7 +12,7 @@ - + \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java b/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java index c5a3b402bd..5e96f06ac8 100644 --- a/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/external/provider/impl/OpenAIREFundingDataProviderTest.java @@ -15,13 +15,12 @@ import java.util.List; import java.util.Optional; import org.dspace.AbstractDSpaceTest; -import org.dspace.external.MockOpenAIRERestConnector; +import org.dspace.external.factory.ExternalServiceFactory; import org.dspace.external.model.ExternalDataObject; +import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.service.ExternalDataService; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.junit.MockitoJUnitRunner; /** * Unit tests for OpenAIREFundingDataProvider @@ -29,11 +28,10 @@ import org.mockito.junit.MockitoJUnitRunner; * @author pgraca * */ -@RunWith(MockitoJUnitRunner.class) public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { - @InjectMocks - OpenAIREFundingDataProvider openAIREFundingDataProvider; + ExternalDataService externalDataService; + ExternalDataProvider openAIREFundingDataProvider; /** * This method will be run before every test as per @Before. It will initialize @@ -44,8 +42,9 @@ public class OpenAIREFundingDataProviderTest extends AbstractDSpaceTest { */ @Before public void init() { - openAIREFundingDataProvider.setSourceIdentifier("openAIREFunding"); - openAIREFundingDataProvider.setConnector(new MockOpenAIRERestConnector("https://api.openaire.eu")); + // Set up External Service Factory and set data providers + externalDataService = ExternalServiceFactory.getInstance().getExternalDataService(); + openAIREFundingDataProvider = externalDataService.getExternalDataProvider("openAIREFunding"); } @Test From e6fce0778ce72443519ec27d9e68f0c334776f36 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 1 Jul 2020 10:58:57 -0500 Subject: [PATCH 0310/1254] Ensure CSVMetadataImportReferenceIT cleans up Items that it creates --- .../app/csv/CSVMetadataImportReferenceIT.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java index f2dcdc697c..0eff180101 100644 --- a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java +++ b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java @@ -12,6 +12,7 @@ import static junit.framework.TestCase.assertEquals; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.io.OutputStreamWriter; import java.sql.SQLException; import java.util.ArrayList; @@ -135,6 +136,9 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat "+,Publication,dc.identifier.other:0," + col1.getHandle() + ",1"}; Item[] items = runImport(csv); assertRelationship(items[1], items[0], 1, "left", 0); + + // remove created items + cleanupImportItems(items); } /** @@ -153,6 +157,20 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat return items; } + /** + * Delete the Items in the given array. This method is used for cleanup after using "runImport" + * @param items items array + * @throws SQLException + * @throws IOException + */ + private void cleanupImportItems(Item[] items) throws SQLException, IOException { + context.turnOffAuthorisationSystem(); + for (Item item: items) { + ItemBuilder.deleteItem(item.getID()); + } + context.restoreAuthSystemState(); + } + /** * Test existence of newly created item with proper relationships defined in the item's metadata via * a rowName reference @@ -165,6 +183,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat "+,Test Item 2,Publication,rowName:idVal," + col1.getHandle() + ",anything,1"}; Item[] items = runImport(csv); assertRelationship(items[1], items[0], 1, "left", 0); + // remove created items + cleanupImportItems(items); } /** @@ -180,6 +200,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat Item[] items = runImport(csv); assertRelationship(items[2], items[0], 1, "left", 0); assertRelationship(items[2], items[1], 1, "left", 1); + // remove created items + cleanupImportItems(items); } /** @@ -195,6 +217,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat Item[] items = runImport(csv); assertRelationship(items[2], items[0], 1, "left", 0); assertRelationship(items[2], items[1], 1, "left", 1); + // remove created items + cleanupImportItems(items); } /** @@ -217,6 +241,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat "+,Publication," + person.getID().toString() + "," + col1.getHandle() + ",anything,0"}; Item[] items = runImport(csv); assertRelationship(items[0], person, 1, "left", 0); + // remove created items + cleanupImportItems(items); } /** @@ -248,6 +274,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat Item[] items = runImport(csv); assertRelationship(items[0], person, 1, "left", 0); assertRelationship(items[0], person2, 1, "left", 1); + // remove created items + cleanupImportItems(items); } /** @@ -273,6 +301,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat Item[] items = runImport(csv); assertRelationship(items[1], person, 1, "left", 0); assertRelationship(items[1], items[0], 1, "left", 1); + // remove created items + cleanupImportItems(items); } /** @@ -310,6 +340,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat assertRelationship(items[1], person, 1, "left", 0); assertRelationship(items[1], person2, 1, "left", 1); assertRelationship(items[1], items[0], 1, "left", 2); + // remove created items + cleanupImportItems(items); } /** @@ -324,6 +356,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat "+,Pub1,Publication,dc.title:Person:," + col1.getHandle() + ",anything,1"}; Item[] items = runImport(csv); assertRelationship(items[1], items[0], 1, "left", 0); + // remove created items + cleanupImportItems(items); } /** @@ -540,6 +574,8 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat Item[] items = runImport(csv); assertRelationship(items[1], items[0], 1, "left", 0); assertRelationship(items[2], items[0], 1, "left", 0); + // remove created items + cleanupImportItems(items); } @Test @@ -644,5 +680,4 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat return uuidList.get(0); } - } From 6ce49eb332e004108d9dd4ee0afdcfbfaeca862c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Sep 2020 16:42:40 -0500 Subject: [PATCH 0311/1254] Fix AuthorizationFeatureServiceIT to extend AbstractControllerIntegrationTest --- .../rest/AuthorizationFeatureServiceIT.java | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java index eba774345d..66be810352 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java @@ -16,7 +16,6 @@ import java.util.List; import java.util.Set; import org.apache.commons.lang3.ArrayUtils; -import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.app.rest.authorization.AlwaysFalseFeature; import org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature; import org.dspace.app.rest.authorization.AlwaysTrueFeature; @@ -27,35 +26,20 @@ 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.utils.DSpaceConfigurationInitializer; -import org.dspace.app.rest.utils.DSpaceKernelInitializer; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.content.Site; import org.dspace.content.service.SiteService; import org.junit.Test; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.context.web.WebAppConfiguration; + /** * Test for the Authorization Feature Service - * + * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ -//Run tests with JUnit 4 and Spring TestContext Framework -@RunWith(SpringRunner.class) -//Specify main class to use to load Spring ApplicationContext -//NOTE: By default, Spring caches and reuses ApplicationContext for each integration test (to speed up tests) -//See: https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#integration-testing -@SpringBootTest(classes = Application.class) -//Load DSpace initializers in Spring ApplicationContext (to initialize DSpace Kernel & Configuration) -@ContextConfiguration(initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class }) -//Tell Spring to make ApplicationContext an instance of WebApplicationContext (for web-based tests) -@WebAppConfiguration -public class AuthorizationFeatureServiceIT extends AbstractIntegrationTestWithDatabase { +public class AuthorizationFeatureServiceIT extends AbstractControllerIntegrationTest { @Autowired private SiteService siteService; From 851161cfd9fcae4663ae84bdbafac023e14fbb6e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Sep 2020 16:45:49 -0500 Subject: [PATCH 0312/1254] General minor code cleanup in ITs --- .../dspace/app/rest/InitializeEntitiesIT.java | 18 +++++------------- .../app/rest/ProcessRestRepositoryIT.java | 7 ------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java index f1cd0866b5..b7be1e9eb2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java @@ -15,7 +15,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.File; -import java.util.Iterator; import java.util.List; import org.dspace.app.rest.matcher.RelationshipTypeMatcher; @@ -67,6 +66,7 @@ public class InitializeEntitiesIT extends AbstractControllerIntegrationTest { } @After + @Override public void destroy() throws Exception { //Clean up the database for the next test context.turnOffAuthorisationSystem(); @@ -74,26 +74,18 @@ public class InitializeEntitiesIT extends AbstractControllerIntegrationTest { List entityTypeList = entityTypeService.findAll(context); List relationships = relationshipService.findAll(context); - Iterator relationshipIterator = relationships.iterator(); - while (relationshipIterator.hasNext()) { - Relationship relationship = relationshipIterator.next(); - relationshipIterator.remove(); + for (Relationship relationship : relationships) { relationshipService.delete(context, relationship); } - Iterator relationshipTypeIterator = relationshipTypeList.iterator(); - while (relationshipTypeIterator.hasNext()) { - RelationshipType relationshipType = relationshipTypeIterator.next(); - relationshipTypeIterator.remove(); + for (RelationshipType relationshipType : relationshipTypeList) { relationshipTypeService.delete(context, relationshipType); } - Iterator entityTypeIterator = entityTypeList.iterator(); - while (entityTypeIterator.hasNext()) { - EntityType entityType = entityTypeIterator.next(); - entityTypeIterator.remove(); + for (EntityType entityType: entityTypeList) { entityTypeService.delete(context, entityType); } + context.restoreAuthSystemState(); super.destroy(); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java index ab7e675d48..5ac416e606 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java @@ -36,7 +36,6 @@ import org.dspace.scripts.Process; import org.dspace.scripts.ProcessLogLevel; import org.dspace.scripts.service.ProcessService; import org.hamcrest.Matchers; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -807,10 +806,4 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { } - - @After - @Override - public void destroy() throws Exception { - super.destroy(); - } } From 4856acde2c0c9c2e90211386ef4242c1e0577afe Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Sep 2020 16:50:16 -0500 Subject: [PATCH 0313/1254] RelationshipTypeBuilder is cleaning up more than it should. Place it after ItemBuilder cleanup so that Items are removed prior to RelationshipTypes --- .../java/org/dspace/builder/RelationshipTypeBuilder.java | 7 ------- .../dspace/builder/util/AbstractBuilderCleanupUtil.java | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/RelationshipTypeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RelationshipTypeBuilder.java index fa1e3b4766..2774f87aa8 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RelationshipTypeBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RelationshipTypeBuilder.java @@ -8,13 +8,11 @@ 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; import org.dspace.content.EntityType; -import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.service.RelationshipTypeService; import org.dspace.core.Context; @@ -42,11 +40,6 @@ public class RelationshipTypeBuilder extends AbstractBuilder byRelationshipType = relationshipService - .findByRelationshipType(c, relationshipType); - for (Relationship relationship : byRelationshipType) { - relationshipService.delete(c, relationship); - } if (relationshipType != null) { delete(c, relationshipType); } diff --git a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index 718bcb50de..b892e32324 100644 --- a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -57,8 +57,6 @@ public class AbstractBuilderCleanupUtil { private void initMap() { map.put(ResourcePolicyBuilder.class.getName(), new ArrayList<>()); map.put(RelationshipBuilder.class.getName(), new ArrayList<>()); - map.put(RelationshipTypeBuilder.class.getName(), new ArrayList<>()); - map.put(EntityTypeBuilder.class.getName(), new ArrayList<>()); map.put(PoolTaskBuilder.class.getName(), new ArrayList<>()); map.put(WorkflowItemBuilder.class.getName(), new ArrayList<>()); map.put(WorkspaceItemBuilder.class.getName(), new ArrayList<>()); @@ -71,6 +69,8 @@ public class AbstractBuilderCleanupUtil { map.put(CommunityBuilder.class.getName(), new ArrayList<>()); map.put(GroupBuilder.class.getName(), new ArrayList<>()); map.put(EPersonBuilder.class.getName(), new ArrayList<>()); + map.put(RelationshipTypeBuilder.class.getName(), new ArrayList<>()); + map.put(EntityTypeBuilder.class.getName(), new ArrayList<>()); map.put(MetadataFieldBuilder.class.getName(), new ArrayList<>()); map.put(MetadataSchemaBuilder.class.getName(), new ArrayList<>()); map.put(SiteBuilder.class.getName(), new ArrayList<>()); From c484bec18f62c6a981c0c9240dd37b17b7c95a82 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 22 Sep 2021 16:27:10 -0400 Subject: [PATCH 0314/1254] [DS-3952] Implement PUT to accept/deny item requests. Fix errors in IT. --- .../requestitem/RequestItemEmailNotifier.java | 168 ++++++++++++++ .../requestitem/RequestItemServiceImpl.java | 29 ++- .../dspace/app/requestitem/package-info.java | 4 +- .../service/RequestItemService.java | 11 + .../repository/RequestItemRepository.java | 207 ++++++++++++++---- .../app/rest/RequestItemRepositoryIT.java | 149 ++++++++----- dspace/config/spring/api/requestitem.xml | 26 ++- 7 files changed, 476 insertions(+), 118 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java 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 new file mode 100644 index 0000000000..e06586282e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -0,0 +1,168 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +/* + * Copyright 2021 Indiana University. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package org.dspace.app.requestitem; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import javax.mail.MessagingException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.requestitem.factory.RequestItemServiceFactory; +import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.core.LogHelper; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Send item requests and responses by email. + * + * @author Mark H. Wood + */ +public class RequestItemEmailNotifier { + private static final Logger LOG = LogManager.getLogger(); + + private static final BitstreamService bitstreamService + = ContentServiceFactory.getInstance().getBitstreamService(); + + private static final ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + + private static final HandleService handleService + = HandleServiceFactory.getInstance().getHandleService(); + + private static final RequestItemService requestItemService + = RequestItemServiceFactory.getInstance().getRequestItemService(); + + private static final RequestItemAuthorExtractor requestItemAuthorExtractor + = DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName(null, RequestItemAuthorExtractor.class); + + private RequestItemEmailNotifier() {} + + /** + * Send the request to the approver(s). + * + * @param context current DSpace session. + * @param ri the request. + * @param responseLink link back to DSpace to send the response. + * @throws IOException passed through. + * @throws SQLException if the message was not sent. + */ + static public void sendRequest(Context context, RequestItem ri, String responseLink) + throws IOException, SQLException { + // Who is making this request? + RequestItemAuthor author = requestItemAuthorExtractor + .getRequestItemAuthor(context, ri.getItem()); + String authorEmail = author.getEmail(); + String authorName = author.getFullName(); + + // Build an email to the approver. + Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), + "request_item.author")); + email.addRecipient(authorEmail); + email.setReplyTo(ri.getReqEmail()); // Requester's address + email.addArgument(ri.getReqName()); // {0} Requester's name + email.addArgument(ri.getReqEmail()); // {1} Requester's address + email.addArgument(ri.isAllfiles() // {2} All bitstreams or just one? + ? I18nUtil.getMessage("itemRequest.all") : ri.getBitstream().getName()); + email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); + email.addArgument(ri.getItem().getName()); // {4} requested item's title + email.addArgument(ri.getReqMessage()); // {5} message from requester + email.addArgument(responseLink); // {6} Link back to DSpace for action + email.addArgument(authorName); // {7} corresponding author name + email.addArgument(authorEmail); // {8} corresponding author email + email.addArgument(configurationService.getProperty("dspace.name")); + email.addArgument(configurationService.getProperty("mail.helpdesk")); + // Send the email. + try { + email.send(); + LOG.info(LogHelper.getHeader(context, + "sent_email_requestItem", + "submitter_id={},bitstream_id={},requestEmail={}"), + ri.getReqEmail(), ri.getBitstream().getID().toString(), + ri.getReqEmail()); + } catch (MessagingException e) { + LOG.warn(LogHelper.getHeader(context, + "error_mailing_requestItem", e.getMessage())); + throw new IOException("Request not sent: " + e.getMessage()); + } + } + + /** + * Send the approver's response back to the requester, with files attached + * if approved. + * + * @param context current DSpace session. + * @param ri the request. + * @param subject email subject header value. + * @param message email body (may be empty). + * @throws IOException if sending failed. + */ + static public void sendResponse(Context context, RequestItem ri, String subject, + String message) + throws IOException { + // Build an email back to the requester. + Email email = new Email(); + email.setContent("body", message); + email.setSubject(subject); + email.addRecipient(ri.getReqEmail()); + if (ri.isAccept_request()) { + // Attach bitstreams. + try { + if (ri.isAllfiles()) { + Item item = ri.getItem(); + List bundles = item.getBundles("ORIGINAL"); + for (Bundle bundle : bundles) { + List bitstreams = bundle.getBitstreams(); + for (Bitstream bitstream : bitstreams) { + if (!bitstream.getFormat(context).isInternal() && + requestItemService.isRestricted(context, + bitstream)) { + email.addAttachment(bitstreamService.retrieve(context, + bitstream), bitstream.getName(), + bitstream.getFormat(context).getMIMEType()); + } + } + } + } else { + Bitstream bitstream = ri.getBitstream(); + email.addAttachment(bitstreamService.retrieve(context, bitstream), + 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()); + } + } + LOG.info(LogHelper.getHeader(context, + "sent_attach_requestItem", "token={}"), ri.getToken()); + } +} 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 974d9979b0..d2b249f6ec 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 @@ -15,8 +15,13 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.dao.RequestItemDAO; import org.dspace.app.requestitem.service.RequestItemService; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogHelper; import org.dspace.core.Utils; @@ -37,13 +42,20 @@ public class RequestItemServiceImpl implements RequestItemService { @Autowired(required = true) protected RequestItemDAO requestItemDAO; + @Autowired(required = true) + protected AuthorizeService authorizeService; + + @Autowired(required = true) + protected ResourcePolicyService resourcePolicyService; + protected RequestItemServiceImpl() { } @Override - public String createRequest(Context context, Bitstream bitstream, Item item, boolean allFiles, String reqEmail, - String reqName, String reqMessage) throws SQLException { + public String createRequest(Context context, Bitstream bitstream, Item item, + boolean allFiles, String reqEmail, String reqName, String reqMessage) + throws SQLException { RequestItem requestItem = requestItemDAO.create(context, new RequestItem()); requestItem.setToken(Utils.generateHexKey()); @@ -97,4 +109,17 @@ public class RequestItemServiceImpl implements RequestItemService { log.error(e.getMessage()); } } + + @Override + public boolean isRestricted(Context context, DSpaceObject o) + throws SQLException { + List policies = authorizeService + .getPoliciesActionFilter(context, o, Constants.READ); + for (ResourcePolicy rp : policies) { + if (resourcePolicyService.isDateValid(rp)) { + return false; + } + } + return true; + } } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java b/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java index 2e78ecba9e..5886f16fde 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/package-info.java @@ -5,7 +5,6 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.requestitem; /** * Feature for conveying a request that materials forbidden to the requester @@ -13,9 +12,10 @@ package org.dspace.app.requestitem; * e-mailed to a responsible party for consideration and action. Find details * in the user documentation under the rubric "Request a Copy". * - * This package includes several "strategy" classes which discover responsible + *

    This package includes several "strategy" classes which discover responsible * parties in various ways. See {@link RequestItemSubmitterStrategy} and the * classes which extend it. A strategy class must be configured and identified * as {@link RequestItemAuthorExtractor} for injection into code which requires * Request a Copy services. */ +package org.dspace.app.requestitem; 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 d8493125ba..5cab72e4e9 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 @@ -12,6 +12,7 @@ import java.util.List; import org.dspace.app.requestitem.RequestItem; import org.dspace.content.Bitstream; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.core.Context; @@ -76,4 +77,14 @@ public interface RequestItemService { * @param request record to be removed. */ public void delete(Context context, RequestItem request); + + /** + * Is there at least one valid READ resource policy for this object? + * @param context current DSpace session. + * @param o the object. + * @return true if a READ policy applies. + * @throws SQLException passed through. + */ + public boolean isRestricted(Context context, DSpaceObject o) + throws SQLException; } 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 bc8e652bd6..efb71cddb5 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 @@ -11,14 +11,25 @@ package org.dspace.app.rest.repository; import static org.apache.commons.lang3.StringUtils.isBlank; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.sql.SQLException; +import java.util.Date; import java.util.UUID; import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.validator.routines.EmailValidator; +import org.apache.http.client.utils.URIBuilder; +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; import org.dspace.app.rest.converter.RequestItemConverter; import org.dspace.app.rest.exception.IncompleteItemRequestException; @@ -32,6 +43,8 @@ import org.dspace.content.Item; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -46,6 +59,8 @@ import org.springframework.stereotype.Component; @Component(RequestItemRest.CATEGORY + '.' + RequestItemRest.NAME) public class RequestItemRepository extends DSpaceRestRepository { + private static final Logger LOG = LogManager.getLogger(); + @Autowired(required = true) protected RequestItemService requestItemService; @@ -58,6 +73,12 @@ public class RequestItemRepository @Autowired(required = true) protected RequestItemConverter requestItemConverter; + @Autowired(required = true) + protected RequestItemAuthorExtractor requestItemAuthorExtractor; + + @Autowired(required = true) + protected ConfigurationService configurationService; + /* * DSpaceRestRepository */ @@ -80,7 +101,8 @@ public class RequestItemRepository @Override @PreAuthorize("permitAll()") - public RequestItemRest createAndReturn(Context ctx) { + public RequestItemRest createAndReturn(Context ctx) + throws AuthorizeException, SQLException { // Fill a RequestItemRest from the client's HTTP request. HttpServletRequest req = getRequestService() .getCurrentRequest() @@ -93,55 +115,84 @@ public class RequestItemRepository throw new UnprocessableEntityException("error parsing the body", ex); } - // Create the item request model object from the REST object. - String token; - try { - String bitstreamId = rir.getBitstreamId(); - if (isBlank(bitstreamId)) { - throw new IncompleteItemRequestException("A bitstream ID is required"); - } - Bitstream bitstream = bitstreamService.find(ctx, UUID.fromString(bitstreamId)); - if (null == bitstream) { - throw new IncompleteItemRequestException("That bitstream does not exist"); - } - - String itemId = rir.getItemId(); - if (isBlank(itemId)) { - throw new IncompleteItemRequestException("An item ID is required"); - } - Item item = itemService.find(ctx, UUID.fromString(itemId)); - if (null == item) { - throw new IncompleteItemRequestException("That item does not exist"); - } - - boolean allFiles = rir.isAllfiles(); - - String email = rir.getRequestEmail(); - if (isBlank(email)) { - throw new IncompleteItemRequestException("A submitter's email address is required"); - } - EmailValidator emailValidator = EmailValidator.getInstance(false, false); - if (!emailValidator.isValid(email)) { - throw new UnprocessableEntityException("Invalid email address"); - } - - // Escape username to evade nasty XSS attempts - String username = StringEscapeUtils.escapeHtml4(rir.getRequestName()); - - // Escape message text to evade nasty XSS attempts - String message = StringEscapeUtils.escapeHtml4(rir.getRequestMessage()); - - token = requestItemService.createRequest(ctx, bitstream, item, - allFiles, email, username, message); - } catch (SQLException ex) { - throw new RuntimeException("Item request not created.", ex); + // Check request.item.type: + // "all" = anyone can request. + // "logged" = only authenticated user can request. + EPerson user = ctx.getCurrentUser(); + String allowed = configurationService.getProperty("request.item.type", "logged"); + if ("logged".equalsIgnoreCase(allowed) && null == user) { + throw new AuthorizeException("Anonymous requests are not permitted."); } + // Create the item request model object from the REST object. + String token; + String bitstreamId = rir.getBitstreamId(); + if (isBlank(bitstreamId)) { + throw new IncompleteItemRequestException("A bitstream ID is required"); + } + Bitstream bitstream = bitstreamService.find(ctx, UUID.fromString(bitstreamId)); + if (null == bitstream) { + throw new IncompleteItemRequestException("That bitstream does not exist"); + } + + String itemId = rir.getItemId(); + if (isBlank(itemId)) { + throw new IncompleteItemRequestException("An item ID is required"); + } + Item item = itemService.find(ctx, UUID.fromString(itemId)); + if (null == item) { + throw new IncompleteItemRequestException("That item does not exist"); + } + + boolean allFiles = rir.isAllfiles(); + + String email = rir.getRequestEmail(); + if (isBlank(email)) { + throw new IncompleteItemRequestException("A submitter's email address is required"); + } + EmailValidator emailValidator = EmailValidator.getInstance(false, false); + if (!emailValidator.isValid(email)) { + throw new UnprocessableEntityException("Invalid email address"); + } + + // If there is a current user, replace email and name + String username; + if (null != user) { + username = user.getFullName(); + email = user.getEmail(); + } else { + // Escape username to evade nasty XSS attempts + username = StringEscapeUtils.escapeHtml4(rir.getRequestName()); + } + + // Escape message text to evade nasty XSS attempts + String message = StringEscapeUtils.escapeHtml4(rir.getRequestMessage()); + + token = requestItemService.createRequest(ctx, bitstream, item, + allFiles, email, username, message); // Some fields are given values during creation, so return created request. RequestItem ri = requestItemService.findByToken(ctx, token); ri.setAccept_request(false); // Not accepted yet. Must set: DS-4032 requestItemService.update(ctx, ri); + + // Create a link back to DSpace for the approver's response. + String responseLink; + try { + responseLink = getLinkTokenEmail(ri.getToken()); + } catch (URISyntaxException | MalformedURLException e) { + LOG.warn("Impossible URL error while composing email: {}", + e.getMessage()); + throw new RuntimeException("Request not sent: " + e.getMessage()); + } + + // Send the request email + try { + RequestItemEmailNotifier.sendRequest(ctx, ri, responseLink); + } catch (IOException | SQLException ex) { + throw new RuntimeException("Request not sent.", ex); + } + return requestItemConverter.convert(ri, Projection.DEFAULT); } @@ -152,8 +203,78 @@ public class RequestItemRepository throw new RepositoryMethodNotImplementedException(RequestItemRest.NAME, "delete"); } + @Override + @PreAuthorize("permitAll()") + public RequestItemRest put(Context context, HttpServletRequest request, + String apiCategory, String model, String token, JsonNode requestBody) { + RequestItem ri = requestItemService.findByToken(context, token); + if (null == ri) { + throw new UnprocessableEntityException("Item request not found"); + } + + // 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())) { + throw new RuntimeException("Not authorized to approve this request"); + } + + // Make the changes + JsonNode acceptRequestNode = requestBody.findValue("acceptRequest"); + if (null == acceptRequestNode) { + throw new UnprocessableEntityException("acceptRequest is required"); + } else { + ri.setAccept_request(acceptRequestNode.asBoolean()); + } + + JsonNode responseMessageNode = requestBody.findValue("responseMessage"); + String message = responseMessageNode.asText(); + + ri.setDecision_date(new Date()); + requestItemService.update(context, ri); + + // Send the response email + String subject = requestBody.findValue("subject").asText(); + try { + RequestItemEmailNotifier.sendResponse(context, ri, subject, message); + } catch (IOException ex) { + throw new RuntimeException("Response not sent", ex); + } + + RequestItemRest rir = requestItemConverter.convert(ri, Projection.DEFAULT); + return rir; + } + @Override public Class getDomainClass() { return RequestItemRest.class; } + + + /** + * Generate a link back to DSpace, to act on a request. + * + * @param token identifies the request. + * @return URL to the item request API, with the token as request parameter + * "token". + * @throws URISyntaxException passed through. + * @throws MalformedURLException passed through. + */ + private String getLinkTokenEmail(String token) + throws URISyntaxException, MalformedURLException { + String base = configurationService.getProperty("dspace.server.url"); + + URI link = new URIBuilder(base) + .setPath("/api/" + RequestItemRest.CATEGORY + + '/' + RequestItemRest.PLURAL_NAME) + .addParameter("token", token) + .build(); + + return link.toURL().toExternalForm(); + } } 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 221d72b977..8845fc9b29 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 @@ -18,6 +18,7 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock 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.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; @@ -28,6 +29,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.Cookie; @@ -65,6 +67,10 @@ public class RequestItemRepositoryIT + RequestItemRest.CATEGORY + '/' + RequestItemRest.PLURAL_NAME; + public static final String URI_SINGULAR_ROOT = REST_SERVER_URL + + RequestItemRest.CATEGORY + '/' + + RequestItemRest.NAME; + @Autowired(required = true) RequestItemConverter requestItemConverter; @@ -137,7 +143,7 @@ public class RequestItemRepositoryIT .build(); // Test: can we find it? - String authToken = getAuthToken(admin.getEmail(), password); + String authToken = getAuthToken(eperson.getEmail(), password); final String uri = URI_ROOT + '/' + request.getToken(); getClient(authToken).perform(get(uri)) .andExpect(status().isOk()) // Can we find it? @@ -201,14 +207,14 @@ public class RequestItemRepositoryIT RequestItemRest rir = new RequestItemRest(); rir.setBitstreamId(bitstream.getID().toString()); rir.setItemId(item.getID().toString()); - rir.setRequestEmail(RequestItemBuilder.REQ_EMAIL); + rir.setRequestEmail(eperson.getEmail()); rir.setRequestMessage(RequestItemBuilder.REQ_MESSAGE); - rir.setRequestName(RequestItemBuilder.REQ_NAME); + rir.setRequestName(eperson.getFullName()); rir.setAllfiles(false); // Create it and see if it was created correctly. ObjectMapper mapper = new ObjectMapper(); - String authToken = getAuthToken(admin.getEmail(), password); + String authToken = getAuthToken(eperson.getEmail(), password); AtomicReference requestTokenRef = new AtomicReference<>(); try { getClient(authToken) @@ -221,9 +227,9 @@ public class RequestItemRepositoryIT hasJsonPath("$.id", not(is(emptyOrNullString()))), hasJsonPath("$.type", is(RequestItemRest.NAME)), hasJsonPath("$.token", not(is(emptyOrNullString()))), - hasJsonPath("$.requestEmail", is(RequestItemBuilder.REQ_EMAIL)), + hasJsonPath("$.requestEmail", is(eperson.getEmail())), hasJsonPath("$.requestMessage", is(RequestItemBuilder.REQ_MESSAGE)), - hasJsonPath("$.requestName", is(RequestItemBuilder.REQ_NAME)), + hasJsonPath("$.requestName", is(eperson.getFullName())), hasJsonPath("$.allfiles", is(false)), // TODO should be an ISO datetime hasJsonPath("$.requestDate", not(is(emptyOrNullString()))), @@ -307,72 +313,66 @@ public class RequestItemRepositoryIT RequestItemRest rir = new RequestItemRest(); rir.setBitstreamId(bitstream.getID().toString()); rir.setItemId(item.getID().toString()); - rir.setRequestEmail(RequestItemBuilder.REQ_EMAIL); + rir.setRequestEmail(eperson.getEmail()); rir.setRequestMessage(RequestItemBuilder.REQ_MESSAGE); - rir.setRequestName(RequestItemBuilder.REQ_NAME); + rir.setRequestName(eperson.getFullName()); rir.setAllfiles(false); // Try to create it, with various malformations. ObjectMapper mapper = new ObjectMapper(); - String authToken = getAuthToken(admin.getEmail(), password); - AtomicReference requestTokenRef = new AtomicReference<>(); + String authToken = getAuthToken(eperson.getEmail(), password); - try { - // Test missing bitstream ID - rir.setBitstreamId(null); - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isUnprocessableEntity()); + // Test missing bitstream ID + rir.setBitstreamId(null); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); - // Test unknown bitstream ID - rir.setBitstreamId(UUID.randomUUID().toString()); - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isUnprocessableEntity()); + // Test unknown bitstream ID + rir.setBitstreamId(UUID.randomUUID().toString()); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); - rir.setBitstreamId(bitstream.getID().toString()); + rir.setBitstreamId(bitstream.getID().toString()); - // Test missing item ID - rir.setItemId(null); - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isUnprocessableEntity()); + // Test missing item ID + rir.setItemId(null); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); - // Test unknown item ID - rir.setItemId(UUID.randomUUID().toString()); - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isUnprocessableEntity()); + // Test unknown item ID + rir.setItemId(UUID.randomUUID().toString()); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); - rir.setItemId(item.getID().toString()); + rir.setItemId(item.getID().toString()); - // Test missing email - rir.setRequestEmail(null); - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isUnprocessableEntity()); + // Test missing email + rir.setRequestEmail(null); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); - // Test bad email - rir.setRequestEmail(""); - getClient(authToken) - .perform(post(URI_ROOT) - .content(mapper.writeValueAsBytes(rir)) - .contentType(contentType)) - .andExpect(status().isUnprocessableEntity()); - } finally { - // Clean up the created request. - RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); - } + // Test bad email + rir.setRequestEmail(""); + getClient(authToken) + .perform(post(URI_ROOT) + .content(mapper.writeValueAsBytes(rir)) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); } /** @@ -472,6 +472,37 @@ public class RequestItemRepositoryIT .andExpect(status().isNoContent()); } + /** + * Test of put method, of class RequestItemRepository. + * @throws java.lang.Exception passed through. + */ + @Test + public void testPut() + throws Exception { + System.out.println("put"); + + // Create an item request to approve. + RequestItem itemRequest = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .build(); + + // Create the HTTP request body. + Map parameters = Map.of( + "acceptRequest", "true", + "subject", "subject", + "responseMessage", "Request accepted"); + String content = new ObjectMapper() + .writer() + .writeValueAsString(parameters); + + // Send the request. + getClient().perform(put(URI_ROOT + '/' + itemRequest.getToken()) + .contentType(contentType) + .content(content)) + .andExpect(status().isOk() + ); + } + /** * Test of getDomainClass method, of class RequestItemRepository. */ diff --git a/dspace/config/spring/api/requestitem.xml b/dspace/config/spring/api/requestitem.xml index cd18add16d..1288753c85 100644 --- a/dspace/config/spring/api/requestitem.xml +++ b/dspace/config/spring/api/requestitem.xml @@ -8,19 +8,21 @@ http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire-candidates="*Service,*DAO,javax.sql.DataSource"> - + - - - + + + - + From 3252b541f824eb3e8e67687bced2e60e8893bc78 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 26 Sep 2021 12:46:45 -0700 Subject: [PATCH 0331/1254] Updated javadocs and changed generator interface. --- .../app/rest/iiif/cache/CacheConfig.java | 5 + .../model/generator/AnnotationGenerator.java | 51 +++++---- .../generator/AnnotationListGenerator.java | 31 ++++-- .../model/generator/BehaviorGenerator.java | 6 +- .../iiif/model/generator/CanvasGenerator.java | 43 ++++---- .../model/generator/CanvasItemsGenerator.java | 48 ++++----- .../generator/ContentAsTextGenerator.java | 9 +- .../generator/ContentSearchGenerator.java | 31 +++--- .../generator/ExternalLinksGenerator.java | 39 +++---- .../iiif/model/generator/IIIFResource.java | 6 +- .../iiif/model/generator/IIIFService.java | 6 +- .../rest/iiif/model/generator/IIIFValue.java | 6 +- .../generator/ImageContentGenerator.java | 12 ++- .../generator/ImageServiceGenerator.java | 14 +-- .../model/generator/ManifestGenerator.java | 100 +++++++++--------- .../generator/MetadataEntryGenerator.java | 12 ++- .../model/generator/ProfileGenerator.java | 7 +- .../generator/PropertyValueGenerator.java | 9 +- .../iiif/model/generator/RangeGenerator.java | 32 ++++-- .../generator/SearchResultGenerator.java | 10 +- .../iiif/service/AbstractResourceService.java | 2 +- .../iiif/service/AnnotationListService.java | 21 ++-- .../iiif/service/CanvasLookupService.java | 8 +- .../app/rest/iiif/service/CanvasService.java | 17 +-- .../iiif/service/ImageContentService.java | 8 +- .../rest/iiif/service/ManifestService.java | 68 +++++------- .../app/rest/iiif/service/RangeService.java | 7 +- .../app/rest/iiif/service/RelatedService.java | 17 +-- .../app/rest/iiif/service/SearchService.java | 6 +- .../app/rest/iiif/service/SeeAlsoService.java | 13 +-- .../rest/iiif/service/SequenceService.java | 33 +++--- .../iiif/service/WordHighlightSolrSearch.java | 20 ++-- 32 files changed, 381 insertions(+), 316 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java index 56a1851eef..1f91623b25 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java @@ -10,6 +10,11 @@ package org.dspace.app.rest.iiif.cache; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Configuration; +/** + * Enables Spring cache support. The configuration file is defined in + * application properties. Cache size limits are defined there. + *

    spring.cache.jcache.config=classpath:iiif/cache/ehcache.xml

    + */ @Configuration @EnableCaching public class CacheConfig { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java index ea7cc712fa..31b2abc5c8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java @@ -10,14 +10,16 @@ package org.dspace.app.rest.iiif.model.generator; import java.util.ArrayList; import java.util.List; +import javax.validation.constraints.NotNull; + import de.digitalcollections.iiif.model.Motivation; import de.digitalcollections.iiif.model.openannotation.Annotation; import de.digitalcollections.iiif.model.sharedcanvas.Resource; /** - * Annotations associate content resources and commentary with a canvas. - * This is used for the otherContent AnnotationList and Search response. + * Generator for an {@code annotation} model. Annotations associate content resources and commentary with a canvas. + * This is used for the {@code seeAlso} annotation and Search response. */ public class AnnotationGenerator implements IIIFResource { @@ -34,28 +36,33 @@ public class AnnotationGenerator implements IIIFResource { List manifests = new ArrayList<>(); - /** - * Set the annotation identifier. Required. - * @param identifier - * @return - */ - public AnnotationGenerator setIdentifier(String identifier) { + public AnnotationGenerator(@NotNull String identifier) { + if (identifier.isEmpty()) { + throw new RuntimeException("Invalid annotation identifier. Cannot be an empty string."); + } this.identifier = identifier; - return this; + } + + public AnnotationGenerator(@NotNull String identifier, @NotNull Motivation motivation) { + if (identifier.isEmpty()) { + throw new RuntimeException("Invalid annotation identifier. Cannot be an empty string."); + } + this.identifier = identifier; + this.motivation = motivation; } /** - * Sets the annotation motivtion. Required. - * @param motivation + * Sets the motivation field. Required. + * @param motivation the motivation * @return */ - public AnnotationGenerator setMotivation(Motivation motivation) { + public AnnotationGenerator setMotivation(@NotNull Motivation motivation) { this.motivation = motivation; return this; } /** - * Set the canvas for this annotation. + * Sets the canvas that is associated with this annotation. * @param canvas * @return */ @@ -65,7 +72,7 @@ public class AnnotationGenerator implements IIIFResource { } /** - * Set a text resource for this annotation. + * Sets a text resource for this annotation. * @param contentAsText * @return */ @@ -75,8 +82,8 @@ public class AnnotationGenerator implements IIIFResource { } /** - * Set the external link for this annotation. - * @param otherContent + * Sets an external link for this annotation. + * @param otherContent external link generator * @return */ public AnnotationGenerator setResource(ExternalLinksGenerator otherContent) { @@ -87,32 +94,34 @@ public class AnnotationGenerator implements IIIFResource { /** * Set the within property for this annotation. This property * is a list of manifests. The property is renamed to partOf in v3 + *

    Used by search result annotations.

    * @param within * @return */ public AnnotationGenerator setWithin(List within) { for (ManifestGenerator manifest : within) { - this.manifests.add(manifest.getResource()); + this.manifests.add(manifest.generate()); } return this; } @Override - public Resource getResource() { + public Resource generate() { if (identifier == null || motivation == null) { throw new RuntimeException("Annotations require both an identifier and a motivation"); } Annotation annotation = new Annotation(identifier, motivation); + annotation.setWithin(manifests); // These optional annotation fields vary with the context. if (canvasGenerator != null) { - annotation.setOn(canvasGenerator.getResource()); + annotation.setOn(canvasGenerator.generate()); } if (externalLinksGenerator != null) { - annotation.setResource(externalLinksGenerator.getResource()); + annotation.setResource(externalLinksGenerator.generate()); } if (contentAsTextGenerator != null) { - annotation.setResource(contentAsTextGenerator.getResource()); + annotation.setResource(contentAsTextGenerator.generate()); } return annotation; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java index be6faa970c..a107315fe3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java @@ -10,39 +10,50 @@ package org.dspace.app.rest.iiif.model.generator; import java.util.ArrayList; import java.util.List; +import javax.validation.constraints.NotNull; + import de.digitalcollections.iiif.model.openannotation.Annotation; import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList; import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; /** - * An ordered list of annotations. Annotation Lists are separate resources that - * should be dereferenced when encountered. - * - * This class is used when retrieving an AnnotationList referenced in the Manifest. + * This generator wraps the domain model for the {@code AnnotationList}. There should be a single instance of + * this object per annotation list request. The {@code @RequestScope} provides a single instance created + * and available during complete lifecycle of the HTTP request. + *

    + * The model represents an ordered list of annotations.

    */ +@RequestScope @Component -@Scope("prototype") public class AnnotationListGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { private String identifier; private List annotations = new ArrayList<>(); - public void setIdentifier(String identifier) { + /** + * Sets the required annotation identifier. + * @param identifier the annotation identifier + */ + public void setIdentifier(@NotNull String identifier) { + this.identifier = identifier; } /** * Adds Annotation resource to the annotation list. - * @param annotation the Annotation Resource + * @param annotation an annotation generator */ public void addResource(AnnotationGenerator annotation) { - this.annotations.add((Annotation) annotation.getResource()); + this.annotations.add((Annotation) annotation.generate()); } @Override - public Resource getResource() { + public Resource generate() { + if (identifier == null) { + throw new RuntimeException("Missing the required identifier for the annotation list."); + } AnnotationList annotationList = new AnnotationList(identifier); annotationList.setResources(annotations); return annotationList; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java index e11e15d1ab..4f32fff6cd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java @@ -16,17 +16,17 @@ import org.springframework.stereotype.Component; * * With IIIF Presentation API 3.0 the viewingHint property is renamed to "behavior". */ -@Component public class BehaviorGenerator implements IIIFValue { private String type; - public void setType(String type) { + public BehaviorGenerator setType(String type) { this.type = type; + return this; } @Override - public ViewingHint getValue() { + public ViewingHint generate() { if (type == null) { throw new RuntimeException("Type must be provided for viewing hint."); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index 949efab1ac..0b10410dcd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -10,32 +10,37 @@ package org.dspace.app.rest.iiif.model.generator; import java.util.ArrayList; import java.util.List; +import javax.validation.constraints.NotNull; + import de.digitalcollections.iiif.model.ImageContent; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; /** - * Presentation API version 2.1.1 Canvas model. - * - * Changes a Presentation API version 3.0 will likely require updates for - * multiple media types, etc. + * This generator wraps the domain model for a single {@code Canvas}. */ public class CanvasGenerator implements IIIFResource { - private String identifier; + private final String identifier; + private final List images = new ArrayList(); private String label; private Integer height; private Integer width; - private List images = new ArrayList(); private ImageContent thumbnail; - public CanvasGenerator setIdentifier(String identifier) { + /** + * Constructor + * @param identifier the canvas identifier + */ + public CanvasGenerator(@NotNull String identifier) { + if (identifier.isEmpty()) { + throw new RuntimeException("Invalid canvas identifier. Cannot be an empty string."); + } this.identifier = identifier; - return this; } /** - * Every canvas must have a label to display. + * Adds a canvas label. * @param label */ public CanvasGenerator setLabel(String label) { @@ -44,8 +49,8 @@ public class CanvasGenerator implements IIIFResource { } /** - * Every canvas must have an integer height. - * @param height + * Sets the canvas height. A canvas annotation with motivation {@code sc:painting} must have an pixel height. + * @param height canvas height in pixels */ public CanvasGenerator setHeight(int height) { this.height = height; @@ -53,8 +58,8 @@ public class CanvasGenerator implements IIIFResource { } /** - * Every canvas must have an integer width. - * @param width + * Sets the canvas width. A canvas annotation with motivation {@code sc:painting} must have a pixel width. + * @param width canvas width in pixels */ public CanvasGenerator setWidth(int width) { this.width = width; @@ -62,8 +67,8 @@ public class CanvasGenerator implements IIIFResource { } /** - * Add to ImageContent resources that will be assigned to the canvas. - * @param imageContent + * Add to the list of image content resources for the canvas. + * @param imageContent image content model */ public CanvasGenerator addImage(Resource imageContent) { images.add((ImageContent) imageContent); @@ -71,8 +76,8 @@ public class CanvasGenerator implements IIIFResource { } /** - * The Thumbnail resource that will be assigned to the canvas. - * @param thumbnail + * Adds the thumbnail resource that will be assigned to the canvas. + * @param thumbnail image content model */ public CanvasGenerator addThumbnail(Resource thumbnail) { this.thumbnail = (ImageContent) thumbnail; @@ -80,11 +85,11 @@ public class CanvasGenerator implements IIIFResource { } /** - * Returns the canvas. + * Creates the canvas domain model object. * @return canvas model */ @Override - public Resource getResource() { + public Resource generate() { /** * The Canvas resource typically includes image content. */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java index e0aa59ff7e..5379da8631 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java @@ -14,59 +14,57 @@ import de.digitalcollections.iiif.model.OtherContent; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import de.digitalcollections.iiif.model.sharedcanvas.Sequence; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Facade for the current Presentation API version 2.1.1 domain model's Sequence class. - * - * In Presentation API version 2.1.1, each Manifest includes a single Sequence that defines - * the order of the views of the object. - * - * Sequence is removed with Presentation API version 3.0. Canvases are added to the Manifest - * items property instead. + * This generator wraps the domain model for a Presentation API 2.1.1 {@code Sequence}. There must be a single + * instance of this object per request. The {@code @RequestScope} provides a single instance created and available + * during complete lifecycle of the HTTP request. + *

    + * The IIIF sequence conveys the ordering of the views of the object. + *

    + *

    + * Sequence is removed with Presentation API version 3.0. Canvases are added to the Manifest items property instead. + *

    */ -@Component @RequestScope -public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { +@Component +public class CanvasItemsGenerator implements IIIFResource { private String identifier; private OtherContent rendering; private final List canvas = new ArrayList<>(); - @Autowired - org.dspace.app.rest.iiif.model.generator.BehaviorGenerator viewingHintFascade; /** - * Mandatory. The domain model requires a URI identifier for the sequence. - * @param identifier string for the URI + * Sets the required identifier property. + * @param identifier URI string */ public void setIdentifier(String identifier) { - this.identifier = identifier; } /** - * A link to an external resource intended for display or download by a human user. - * This is typically going to be a PDF file. - * @param otherContent wrapper for OtherContent + * Adds a rendering annotation to the Sequence. The rendering is a link to an external resource intended + * for display or download by a human user. This is typically going to be a PDF file. + * @param otherContent generator for the resource */ - public void addRendering(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { + public void addRendering(ExternalLinksGenerator otherContent) { - this.rendering = (OtherContent) otherContent.getResource(); + this.rendering = (OtherContent) otherContent.generate(); } /** - * Add a Canvas to the sequence. - * @param canvas wrapper for Canvas + * Adds a single {@code Canvas} to the sequence. + * @param canvas generator for canvas */ - public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { - this.canvas.add((Canvas) canvas.getResource()); + public void addCanvas(CanvasGenerator canvas) { + this.canvas.add((Canvas) canvas.generate()); } @Override - public Resource getResource() { + public Resource generate() { Sequence items = new Sequence(identifier); if (rendering != null) { items.addRendering(rendering); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java index 1be4806e43..8170b733f8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java @@ -9,8 +9,13 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.openannotation.ContentAsText; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +/** + * Generator for a text annotation. + */ +@Scope("prototype") @Component public class ContentAsTextGenerator implements IIIFResource { @@ -21,9 +26,9 @@ public class ContentAsTextGenerator implements IIIFResource { } @Override - public Resource getResource() { + public Resource generate() { if (text == null) { - throw new RuntimeException("ContextAsText requires text input."); + throw new RuntimeException("Missing required text for the text annotation."); } return new ContentAsText(text); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java index 1e9d7ba16c..85f821de7b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java @@ -11,6 +11,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import javax.validation.constraints.NotNull; + import de.digitalcollections.iiif.model.Profile; import de.digitalcollections.iiif.model.Service; import de.digitalcollections.iiif.model.search.ContentSearchService; @@ -19,40 +21,43 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Facade for the Search API version 1.0 search service description. - * - * Added to the Manifest when the item supports full-text searching, identified - * by the "dspace.entity.type: IIIFSearchable" DSpace metadata field. NOTE: the - * entity.type is going to be abandoned in favor of another DSO metadata field. + * This generator wraps the search service annotation that is added to + * the manifest for searchable items. Only a single search service is defined + * for the manifest. There should be a single instance of this object per request. + * The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. */ -@Component @RequestScope -public class ContentSearchGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFService { +@Component +public class ContentSearchGenerator implements IIIFService { private String identifier; private String label; @Autowired - org.dspace.app.rest.iiif.model.generator.ProfileGenerator profile; + ProfileGenerator profile; /** * Mandatory URI for search service. * @param identifier */ - public void setIdentifier(String identifier) { + public void setIdentifier(@NotNull String identifier) { + if (identifier.isEmpty()) { + throw new RuntimeException("The search service requires an identifier."); + } this.identifier = identifier; } /** - * Optional label - * @param label + * Optional label for the search service. + * @param label the search service label. */ public void setLabel(String label) { this.label = label; } @Override - public Service getService() { + public Service generate() { if (identifier == null) { throw new RuntimeException("You must provide an identifier for the search service."); } @@ -67,7 +72,7 @@ public class ContentSearchGenerator implements org.dspace.app.rest.iiif.model.ge } ArrayList profiles = new ArrayList<>(); profile.setIdentifier("http://iiif.io/api/search/0/search"); - profiles.add(profile.getValue()); + profiles.add(profile.generate()); contentSearchService.setProfiles(profiles); return contentSearchService; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java index d5bb479e0e..cb68e25299 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java @@ -7,42 +7,35 @@ */ package org.dspace.app.rest.iiif.model.generator; +import javax.validation.constraints.NotNull; + import de.digitalcollections.iiif.model.OtherContent; import de.digitalcollections.iiif.model.PropertyValue; import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; /** - * Facade for the API version 2.1.1 "OtherContent" domain model. + * This generator wraps the other content domain model. * - * This is the type for Content resources such as images or texts that are associated with a canvas. - * Used in the "related", "renderings" and "otherContent" fields of IIIF resources. - * - * IIIF Presentation API version 3.0 removes the otherContent property and uses annotations - * and items instead. + * This is the type for related content resources. Used in the "related", "renderings" and + * "seeAlso" fields of IIIF resources. */ -@Component -@Scope("prototype") public class ExternalLinksGenerator implements IIIFResource { - private String identifier; + private final String identifier; private String format; private String label; private String type; - /** - * Sets the mandatory identifier. - * @param identifier - */ - public ExternalLinksGenerator setIdentifier(String identifier) { + public ExternalLinksGenerator(@NotNull String identifier) { + if (identifier.isEmpty()) { + throw new RuntimeException("Mandatory external links identifier cannot be an empty string"); + } this.identifier = identifier; - return this; } /** - * Sets the optional format. - * @param format + * Sets the optional format value. + * @param format the mimetype */ public ExternalLinksGenerator setFormat(String format) { this.format = format; @@ -51,7 +44,7 @@ public class ExternalLinksGenerator implements IIIFResource { /** * Sets the optional label. - * @param label + * @param label annotation label */ public ExternalLinksGenerator setLabel(String label) { this.label = label; @@ -60,7 +53,7 @@ public class ExternalLinksGenerator implements IIIFResource { /** * Sets the optional type. - * @param type + * @param type the annotation type */ public ExternalLinksGenerator setType(String type) { this.type = type; @@ -68,9 +61,9 @@ public class ExternalLinksGenerator implements IIIFResource { } @Override - public Resource getResource() { + public Resource generate() { if (identifier == null) { - throw new RuntimeException("Annotation requires an identifier"); + throw new RuntimeException("External links annotation requires an identifier"); } OtherContent otherContent; if (format != null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java index c752e5f866..7bf4c89f7e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java @@ -12,9 +12,9 @@ import de.digitalcollections.iiif.model.sharedcanvas.Resource; public interface IIIFResource { /** - * Returns a Resource for serialization. - * @return + * Creates and returns a resource model. + * @return resource model */ - Resource getResource(); + Resource generate(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java index 24d70de119..4ad2e2cfc8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java @@ -12,8 +12,8 @@ import de.digitalcollections.iiif.model.Service; public interface IIIFService { /** - * Returns a Service for serialization. - * @return + * Creates and returns a service model + * @return a service model */ - Service getService(); + Service generate(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java index 2e0cc120bd..240ec0066a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java @@ -10,8 +10,8 @@ package org.dspace.app.rest.iiif.model.generator; public interface IIIFValue { /** - * Returns a value object. - * @return + * creates and returns a value model. + * @return a value model. */ - Object getValue(); + Object generate(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java index 48388af6ec..f36bbb76ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java @@ -7,11 +7,13 @@ */ package org.dspace.app.rest.iiif.model.generator; +import javax.validation.constraints.NotNull; + import de.digitalcollections.iiif.model.ImageContent; import de.digitalcollections.iiif.model.sharedcanvas.Resource; /** - * POJO for the domain model's ImageContent. + * This service generator wraps the image content model. * * Presentation API version 2.1.1: The ImageContent entity is contained in the "resource" * field of annotations with motivation "sc:painting". Image resources, and only image resources, @@ -22,7 +24,7 @@ public class ImageContentGenerator implements IIIFResource { private org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator imageService; private final ImageContent imageContent; - public ImageContentGenerator(String identifier) { + public ImageContentGenerator(@NotNull String identifier) { imageContent = new ImageContent(identifier); } @@ -36,16 +38,16 @@ public class ImageContentGenerator implements IIIFResource { } /** - * Sets the image service that the client will use to retrieve images. + * Adds the IIIF image service annotation. * @param imageService */ public ImageContentGenerator addService(ImageServiceGenerator imageService) { - this.imageContent.addService(imageService.getService()); + this.imageContent.addService(imageService.generate()); return this; } @Override - public Resource getResource() { + public Resource generate() { return imageContent; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java index 8142e0a502..10bc1870ee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java @@ -11,10 +11,10 @@ import de.digitalcollections.iiif.model.Service; import de.digitalcollections.iiif.model.image.ImageService; /** - * POJO facade for API version 2.1.1 image service property. Added to - * each image resource. + * This service generator wraps the image service property model. An image service + * annotation is added to each canvas annotation. */ -public class ImageServiceGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFService { +public class ImageServiceGenerator implements IIIFService { private ImageService imageService; @@ -24,15 +24,15 @@ public class ImageServiceGenerator implements org.dspace.app.rest.iiif.model.gen /** * Sets the IIIF image profile. - * @param profile + * @param profile a profile generator */ - public ImageServiceGenerator setProfile(org.dspace.app.rest.iiif.model.generator.ProfileGenerator profile) { - imageService.addProfile(profile.getValue()); + public ImageServiceGenerator setProfile(ProfileGenerator profile) { + imageService.addProfile(profile.generate()); return this; } @Override - public Service getService() { + public Service generate() { return imageService; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 05b067732f..4f373fb62b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -11,6 +11,8 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; +import javax.validation.constraints.NotNull; + import de.digitalcollections.iiif.model.ImageContent; import de.digitalcollections.iiif.model.MetadataEntry; import de.digitalcollections.iiif.model.OtherContent; @@ -21,19 +23,23 @@ import de.digitalcollections.iiif.model.sharedcanvas.Manifest; import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import de.digitalcollections.iiif.model.sharedcanvas.Sequence; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * The Manifest is an overall description of the structure and properties of the digital representation - * of an object. It carries information needed for the viewer to present the digitized content to the user, - * such as a title and other descriptive information about the object or the intellectual work that - * it conveys. Each manifest describes how to present a single object such as a book, a photograph, - * or a statue. + * This generator wraps a domain model for the {@code Manifest}. There should be a single instance of + * this object per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. + *

    + * The Manifest is an overall description of the structure and properties of the digital representation + * of an object. It carries information needed for the viewer to present the digitized content to the user, + * such as a title and other descriptive information about the object or the intellectual work that + * it conveys. Each manifest describes how to present a single object such as a book, a photograph, + * or a statue. + *

    */ -@Component @RequestScope +@Component public class ManifestGenerator implements IIIFResource { private String identifier; @@ -50,121 +56,117 @@ public class ManifestGenerator implements IIIFResource { private final List metadata = new ArrayList<>(); private List ranges = new ArrayList<>(); - @Autowired - MetadataEntryGenerator metadataEntryGenerator; - - @Autowired - BehaviorGenerator behaviorGenerator; - /** - * Sets the mandatory Manifest ID. - * @param identifier + * Sets the mandatory manifest identifier. + * @param identifier manifest identifier */ - public void setIdentifier(String identifier) { + public void setIdentifier(@NotNull String identifier) { + + if (identifier.isEmpty()) { + throw new RuntimeException("Invalid manifest identifier. Cannot be an empty string."); + } this.identifier = identifier; } /** - * Sets the Manifest label. - * @param label + * Sets the manifest label. + * @param label manifest label */ public void setLabel(String label) { this.label = label; } public void addLogo(ImageContentGenerator logo) { - this.logo = (ImageContent) logo.getResource(); + this.logo = (ImageContent) logo.generate(); } /** - * Sets the behavior. A hint to the client as to the most appropriate method of displaying the resource - * In IIIF Presentation API version 3.0 semantics this is the "behavior" - * @param viewingHint + * Sets the viewing hint. In IIIF Presentation API version 3.0 semantics this becomes the "behavior" + * @param viewingHint a viewing hint */ public void addViewingHint(String viewingHint) { - behaviorGenerator.setType(viewingHint); - this.viewingHint = behaviorGenerator.getValue(); + BehaviorGenerator hint = new BehaviorGenerator().setType(viewingHint); + this.viewingHint = hint.generate(); } /** - * Use to add single mandatory sequence to Manifest. In IIIF Presentation API 3.0 "sequence" + * Adds add single (mandatory) {@ode sequence} to the manifest. In IIIF Presentation API 3.0 "sequence" * is replaced by "items" * @param sequence canvas list model (sequence) */ public void addSequence(CanvasItemsGenerator sequence) { - this.sequence = (Sequence) sequence.getResource(); + this.sequence = (Sequence) sequence.generate(); } /** - * Add otional seeAlso element to Manifest. + * Adds an optional {@code seeAlso} element to Manifest. * @param seeAlso other content model */ public void addSeeAlso(ExternalLinksGenerator seeAlso) { - this.seeAlso = (OtherContent) seeAlso.getResource(); + this.seeAlso = (OtherContent) seeAlso.generate(); } /** - * Add optional thumbnail image to manifest. - * @param thumbnail image content model + * Adds optional thumbnail image resource to manifest. + * @param thumbnail an image content generator */ public void addThumbnail(ImageContentGenerator thumbnail) { - this.thumbnail = (ImageContent) thumbnail.getResource(); + this.thumbnail = (ImageContent) thumbnail.generate(); } /** - * Add optional related element to Manifest. - * @param related other content model + * Adds an optional {@code related} field to the manifest. + * @param related other content generator */ public void addRelated(ExternalLinksGenerator related) { - this.related = (OtherContent) related.getResource(); + this.related = (OtherContent) related.generate(); } /** - * Adds optional search service to Manifest. - * @param searchService search service model + * Adds optional search service to the manifest. + * @param searchService search service generator */ public void addService(ContentSearchGenerator searchService) { - this.searchService = (ContentSearchService) searchService.getService(); + this.searchService = (ContentSearchService) searchService.generate(); } /** - * Adds single metadata field to Manifest. + * Adds a single metadata field to Manifest. * @param field property field * @param value property value */ public void addMetadata(String field, String value) { - metadataEntryGenerator.setField(field); - metadataEntryGenerator.setValue(value); - metadata.add(metadataEntryGenerator.getValue()); + MetadataEntryGenerator meta = new MetadataEntryGenerator().setField(field).setValue(value); + metadata.add(meta.generate()); } /** - * Adds optional license to Manifest. - * @param license + * Adds an optional license to manifest. + * @param license license terms */ public void addLicense(String license) { this.license.add(URI.create(license)); } /** - * Adds optional description to Manifest. + * Adds an optional description to the manifest. * @param field property field * @param value property value */ public void addDescription(String field, String value) { - description = new PropertyValueGenerator().getPropertyValue(field, value).getValue(); + description = new PropertyValueGenerator().getPropertyValue(field, value).generate(); } /** - * Adds optional Range to the manifest's structures element. - * @param rangeGenerator list of range models + * Adds an optional {@code range} to the manifest's {@code structures} element. + * @param rangeGenerator list of range generators */ public void setRange(List rangeGenerator) { ranges = rangeGenerator; } @Override - public Resource getResource() { + public Resource generate() { if (identifier == null) { throw new RuntimeException("The Manifest resource requires an identifier."); @@ -185,7 +187,7 @@ public class ManifestGenerator implements IIIFResource { } if (ranges != null && ranges.size() > 0) { for (RangeGenerator range : ranges) { - manifest.addRange((Range) range.getResource()); + manifest.addRange((Range) range.generate()); } } if (metadata.size() > 0) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java index 53d2987431..b740f36505 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java @@ -10,7 +10,9 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.MetadataEntry; import org.springframework.stereotype.Component; -@Component +/** + * Wraps the domain model metadata property. + */ public class MetadataEntryGenerator implements IIIFValue { private String field; @@ -20,20 +22,22 @@ public class MetadataEntryGenerator implements IIIFValue { * Set metadata field name. * @param field field name */ - public void setField(String field) { + public MetadataEntryGenerator setField(String field) { this.field = field; + return this; } /** * Set metadata value. * @param value metadata value */ - public void setValue(String value) { + public MetadataEntryGenerator setValue(String value) { this.value = value; + return this; } @Override - public MetadataEntry getValue() { + public MetadataEntry generate() { return new MetadataEntry(field, value); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java index 9939fa9561..004f540edb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java @@ -15,10 +15,11 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** - * A profile for an AnnotationList or Service. + * This class wraps the domain model service profile. Spring can inject unique + * instances of this generator into services using the prototype scope. */ -@Component @Scope("prototype") +@Component public class ProfileGenerator implements IIIFValue { private String identifier; @@ -31,7 +32,7 @@ public class ProfileGenerator implements IIIFValue { } @Override - public Profile getValue() { + public Profile generate() { try { return new Profile(new URI(identifier)); } catch (URISyntaxException e) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java index 7aa2e9a89e..d5ba42a9cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java @@ -10,11 +10,8 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.PropertyValue; /** - * Type for strings that are intended to be displayed to the user. - * - *

    Is organized as a mapping of languages to one or more values. See - * http://iiif.io/api/presentation/2.1/#language-of-property-values and - * http://iiif.io/api/presentation/2.1/#html-markup-in-property-values for more information. + * This class wraps the domain model property value annotation. The property is the type for + * strings that are intended to be displayed to the user. */ public class PropertyValueGenerator implements IIIFValue { @@ -31,7 +28,7 @@ public class PropertyValueGenerator implements IIIFValue { } @Override - public PropertyValue getValue() { + public PropertyValue generate() { return propertyValue; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java index ff92f6a7a6..9a6ea16aa8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java @@ -10,18 +10,19 @@ package org.dspace.app.rest.iiif.model.generator; import java.util.ArrayList; import java.util.List; +import javax.validation.constraints.NotNull; + import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; /** + * This generator wraps the domain model for IIIF {@code ranges}. + * * In Presentation API version 2.1.1, adding a range to the manifest allows the client to display a structured * hierarchy to enable the user to navigate within the object without merely stepping through the current sequence. * The rationale for separating ranges from sequences is that there is likely to be overlap between different ranges, * such as the physical structure of a book compared to the textual structure of the work. - * - * This is used to populate the "structures" element of the Manifest. (The REST API service looks to the "info.json" - * file for ranges.) */ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { @@ -33,13 +34,16 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. * Sets mandatory range identifier. * @param identifier range identifier */ - public RangeGenerator setIdentifier(String identifier) { + public RangeGenerator setIdentifier(@NotNull String identifier) { + if (identifier.isEmpty()) { + throw new RuntimeException("Invalid range identifier. Cannot be an empty string."); + } this.identifier = identifier; return this; } /** - * Sets mandatory range label. + * Sets range label. * @param label range label */ public RangeGenerator setLabel(String label) { @@ -48,17 +52,25 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. } /** - * Adds canvas to Range canvas list. - * @param canvas list of canvas models + * Adds canvas to range canvas list. + * @param canvas list of canvas generators */ public RangeGenerator addCanvas(CanvasGenerator canvas) { - canvasList.add((Canvas) canvas.getResource()); + canvasList.add((Canvas) canvas.generate()); return this; } @Override - public Resource getResource() { - Range range = new Range(identifier, label); + public Resource generate() { + if (identifier == null) { + throw new RuntimeException("Missing identifier. Cannot create range."); + } + Range range; + if (label == null) { + range = new Range(identifier); + } else { + range = new Range(identifier, label); + } for (Canvas canvas : canvasList) { range.addCanvas(canvas); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java index 289005ec7b..81facc5446 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java @@ -17,10 +17,12 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Facade for the AnnotationList that contains hits for a given search query. + * This generator wraps a domain model for a {@code SearchResult}. There should be a single instance of + * this object per search request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. */ -@Component @RequestScope +@Component public class SearchResultGenerator implements IIIFResource { private String identifier; @@ -31,11 +33,11 @@ public class SearchResultGenerator implements IIIFResource { } public void addResource(AnnotationGenerator annotation) { - annotations.add((Annotation) annotation.getResource()); + annotations.add((Annotation) annotation.generate()); } @Override - public Resource getResource() { + public Resource generate() { SearchResult searchResult = new SearchResult(identifier); searchResult.setResources(annotations); return searchResult; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 17a8bf3b96..5ef9fd8ec5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -16,7 +16,7 @@ import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; /** - * Base class for IIIF responses. + * Base class for services. */ public abstract class AbstractResourceService { /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java index 9a115f8bf2..ae65edb4c2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java @@ -28,8 +28,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; -@Component +/** + * This service provides methods for creating an {@code Annotation List}. There should be a single instance of + * this service per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. + */ @RequestScope +@Component public class AnnotationListService extends AbstractResourceService { @@ -45,9 +50,6 @@ public class AnnotationListService extends AbstractResourceService { @Autowired BitstreamFormatService bitstreamFormatService; - @Autowired - ExternalLinksGenerator externalLinksGenerator; - @Autowired AnnotationListGenerator annotationList; @@ -96,15 +98,15 @@ public class AnnotationListService extends AbstractResourceService { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - AnnotationGenerator annotationGenerator = new AnnotationGenerator() - .setIdentifier(IIIF_ENDPOINT + bitstream.getID() + "/annot") - .setMotivation(AnnotationGenerator.LINKING) + AnnotationGenerator annotationGenerator = + new AnnotationGenerator(IIIF_ENDPOINT + bitstream.getID() + "/annot", + AnnotationGenerator.LINKING) .setResource(getLinksGenerator(mimetype, bitstream)); annotationList.addResource(annotationGenerator); } } } - return utils.asJson(annotationList.getResource()); + return utils.asJson(annotationList.generate()); } private ExternalLinksGenerator getLinksGenerator(String mimetype, Bitstream bitstream) { @@ -113,8 +115,7 @@ public class AnnotationListService extends AbstractResourceService { + bitstream.getID() + "/content"; - return externalLinksGenerator - .setIdentifier(identifier) + return new ExternalLinksGenerator(identifier) .setFormat(mimetype) .setLabel(bitstream.getName()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java index c22ea91627..16b7941f37 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -22,10 +22,12 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Canvases may be dereferenced separately from the manifest via their IDs. + * This service provides methods for creating a single {@code Canvas}. There should be a single instance of + * this service per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. */ -@Component @RequestScope +@Component public class CanvasLookupService extends AbstractResourceService { @Autowired @@ -51,7 +53,7 @@ public class CanvasLookupService extends AbstractResourceService { CanvasGenerator canvasGenerator = canvasService.getCanvas(item.getID().toString(), bitstreamId, mimeType, info, canvasPosition); - return utils.asJson(canvasGenerator.getResource()); + return utils.asJson(canvasGenerator.generate()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index c43cdbcaa7..039dba635f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -15,11 +15,16 @@ import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; +/** + * This service provides methods for creating {@code Canvases}. There should be a single instance of + * this service per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. + */ +@RequestScope @Component -@Scope("prototype") public class CanvasService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CanvasService.class); @@ -89,9 +94,9 @@ public class CanvasService extends AbstractResourceService { ImageContentGenerator thumb = imageContentService .getImageContent(bitstreamId, mimeType, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); - return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) - .addImage(image.getResource()) - .addThumbnail(thumb.getResource()) + return new CanvasGenerator(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) + .addImage(image.generate()) + .addThumbnail(thumb.generate()) .setHeight(canvasHeight) .setWidth(canvasWidth) .setLabel(label); @@ -105,7 +110,7 @@ public class CanvasService extends AbstractResourceService { * @return */ protected CanvasGenerator getRangeCanvasReference(String identifier, String startCanvas) { - return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + identifier + startCanvas); + return new CanvasGenerator(IIIF_ENDPOINT + identifier + startCanvas); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java index 476ad27474..240cd3767c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java @@ -15,9 +15,15 @@ import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; import org.dspace.services.ConfigurationService; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; +/** + * This service provides methods for creating a {@code Image Resource} annotation. There should be a single instance of + * this service per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. + */ +@RequestScope @Component -@Scope("prototype") public class ImageContentService extends AbstractResourceService { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index cc845b7cf8..21c6726ec8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -28,10 +28,12 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Generates IIIF Manifest JSON response for a DSpace Item. + * This service creates the manifest. There should be a single instance of this service per request. + * The {@code @RequestScope} provides a single instance created and available during complete lifecycle + * of the HTTP request. */ -@Component @RequestScope +@Component public class ManifestService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ManifestService.class); @@ -76,27 +78,25 @@ public class ManifestService extends AbstractResourceService { } /** - * Returns serialized Manifest response for a DSpace item. + * Returns JSON manifest response for a DSpace item. * * @param item the DSpace Item * @param context the DSpace context - * @return Manifest as JSON + * @return manifest as JSON */ public String getManifest(Item item, Context context) { populateManifest(item, context); - return utils.asJson(manifestGenerator.getResource()); + return utils.asJson(manifestGenerator.generate()); } /** - * Populates the Manifest for a DSpace Item. + * Populates the manifest for a DSpace Item. * - * @param item DSpace Item - * @param context DSpace context - * @return manifest object + * @param item the DSpace Item + * @param context the DSpace context + * @return manifest domain object */ private void populateManifest(Item item, Context context) { - // If an IIIF bundle is found it will be used. Otherwise, - // images in the ORIGINAL bundle will be used. List bundles = utils.getIiifBundle(item, IIIF_BUNDLE); List bitstreams = utils.getBitstreams(bundles); Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); @@ -114,25 +114,18 @@ public class ManifestService extends AbstractResourceService { } /** - * Adds a single sequence with canvases and a rendering property (optional). + * Adds a single IIIF sequence with canvases and rendering (optional) to the manifest. * @param item DSpace Item * @param bitstreams list of bitstreams * @param context the DSpace context - * @return a sequence of canvases */ private void addSequence(Item item, List bitstreams, Context context, Info info) { - // After replacing the info object with DSO metadata we might - // update this method to iterate over the bitstreams list, passing - // the individual bitstream and position to revised methods in - // sequenceService and rangeService. But it's hard to try now without more - // work elsewhere. manifestGenerator.addSequence( sequenceService.getSequence(item, bitstreams, context, info)); } /** * Adds DSpace Item metadata to the manifest. - * * @param metadata list of DSpace metadata values */ private void addMetadata(List metadata) { @@ -155,12 +148,8 @@ public class ManifestService extends AbstractResourceService { } /** - * A link to an external resource intended to be displayed directly to the user, - * and is related to the resource that has the related property. Examples might - * include a video or academic paper about the resource, a website, an HTML - * description, and so forth. - * - * This method adds a link to the Item represented in the DSpace Angular UI. + * Adds a related item property to the manifest. The property provides a link + * to the Item record in the DSpace Angular UI. * * @param item the DSpace Item */ @@ -169,7 +158,8 @@ public class ManifestService extends AbstractResourceService { } /** - * A hint to the client as to the most appropriate method of displaying the resource. + * Adds a viewing hint to the manifest. This is a hint to the client as to the most + * appropriate method of displaying the resource. * * @param bitstreamCount count of bitstreams in the IIIF bundle. */ @@ -180,13 +170,9 @@ public class ManifestService extends AbstractResourceService { } /** - * A link to a machine readable document that semantically describes the resource with - * the seeAlso property, such as an XML or RDF description. This document could be used - * for search and discovery or inferencing purposes, or just to provide a longer - * description of the resource. May have one or more external descriptions related to it. - * - * This method appends an AnnotationList of resources found in the Item's OtherContent bundle. - * A typical use case would be METS or ALTO files that describe the resource. + * This method adds into the manifest a {@code seeAlso} reference to additional + * resources found in the Item bundle(s). A typical use case would be METS / ALTO files + * that describe the resource. * * @param item the DSpace Item. */ @@ -198,13 +184,10 @@ public class ManifestService extends AbstractResourceService { } /** - * A link to a service that makes more functionality available for the resource, - * such as the base URI of an associated IIIF Search API service. + * This method adds a search service definition to the manifest when + * the item metadata includes {@code iiif.search.enabled}. * - * This method returns a search service definition. Search scope is the manifest. - * - * @param item DSpace Item - * @return the IIIF search service definition for the item + * @param item the DSpace Item */ private void addSearchService(Item item) { if (utils.isSearchable(item)) { @@ -214,8 +197,7 @@ public class ManifestService extends AbstractResourceService { } /** - * Adds Ranges to manifest structures element. - * Ranges are defined in the info.json file. + * Adds structure element and Range to the manifest. (Removed in 4Science PR) * @param info * @param identifier */ @@ -227,7 +209,7 @@ public class ManifestService extends AbstractResourceService { } /** - * Adds thumbnail to the manifest. Uses first image in bundle. + * Adds thumbnail to the manifest. Uses first image bitstream. * @param bundles image bundles * @param context DSpace context */ @@ -245,7 +227,7 @@ public class ManifestService extends AbstractResourceService { } /** - * If the logo is defined in DSpace configuration add to manifest. + * Adds the logo to the manifest when it is defined in DSpace configuration. */ private void setLogoContainer() { if (IIIF_LOGO_IMAGE != null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java index 76de7d7643..9566aa3025 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java @@ -18,8 +18,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; -@Component +/** + * This service provides methods for creating a {@code Range}. There should be a single instance of this service + * per request. The {@code @RequestScope} provides a single instance created and available during complete lifecycle + * of the HTTP request. + */ @RequestScope +@Component public class RangeService extends AbstractResourceService { @Autowired diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java index ba06c4f720..d215788790 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java @@ -14,8 +14,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; -@Component +/** + * This service provides methods for creating a {@code related} annotation. There should be a single instance of + * this service per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. + */ @RequestScope +@Component public class RelatedService extends AbstractResourceService { private static final String RELATED_ITEM_LABEL = "DSpace item view"; @@ -24,14 +29,10 @@ public class RelatedService extends AbstractResourceService { setConfiguration(configurationService); } - @Autowired - ExternalLinksGenerator externalLinksGenerator; - public ExternalLinksGenerator getRelated(Item item) { String url = CLIENT_URL + "/items/" + item.getID(); - externalLinksGenerator.setIdentifier(url); - externalLinksGenerator.setFormat("text/html"); - externalLinksGenerator.setLabel(RELATED_ITEM_LABEL); - return externalLinksGenerator; + return new ExternalLinksGenerator(url) + .setFormat("text/html") + .setLabel(RELATED_ITEM_LABEL); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index dae4364ecf..b52ed8d53e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -17,10 +17,12 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Implements IIIF Search API queries and responses. + * This service provides methods for creating {@code Search API} response. There should be a single instance of + * this service per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. */ -@Component @RequestScope +@Component public class SearchService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SearchService.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java index e6cdd1dc88..be1d20bd93 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java @@ -16,14 +16,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; - -@Component +/** + * This service provides methods for creating {@code seAlso} external link. There should be a single instance of + * this service per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. + */ @RequestScope +@Component public class SeeAlsoService extends AbstractResourceService { - @Autowired - ExternalLinksGenerator externalLinksGenerator; - private static final String SEE_ALSO_LABEL = "More descriptions of this resource"; public SeeAlsoService(ConfigurationService configurationService) { @@ -31,7 +32,7 @@ public class SeeAlsoService extends AbstractResourceService { } public ExternalLinksGenerator getSeeAlso(UUID itemId) { - return externalLinksGenerator.setIdentifier(IIIF_ENDPOINT + itemId + "/manifest/seeAlso") + return new ExternalLinksGenerator(IIIF_ENDPOINT + itemId + "/manifest/seeAlso") .setType(AnnotationGenerator.TYPE) .setLabel(SEE_ALSO_LABEL); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java index ef0d190ac4..e94bddac26 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java @@ -22,11 +22,16 @@ import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; +/** + * This service provides methods for creating a {@code Sequence}. There should be a single instance of + * this service per request. The {@code @RequestScope} provides a single instance created and available during + * complete lifecycle of the HTTP request. + */ +@RequestScope @Component -@Scope("prototype") public class SequenceService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SequenceService.class); @@ -37,9 +42,6 @@ public class SequenceService extends AbstractResourceService { @Autowired CanvasItemsGenerator sequenceGenerator; - @Autowired - ExternalLinksGenerator externalLinksGenerator; - @Autowired CanvasService canvasService; @@ -48,6 +50,16 @@ public class SequenceService extends AbstractResourceService { setConfiguration(configurationService); } + /** + * Returns a sequence generator that has been configured with canvases and an optional + * rendering link. (@abollini will update.) + * + * @param item the DSpace item + * @param bitstreams list of bitstreams + * @param context the DSpace context + * @param info the info.json file + * @return a sequence generator + */ public CanvasItemsGenerator getSequence(Item item, List bitstreams, Context context, Info info) { sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); @@ -61,7 +73,7 @@ public class SequenceService extends AbstractResourceService { /** * This method adds a canvas to the sequence for each item in the list of DSpace bitstreams. - * Bitstreams must be on image mime type. + * Bitstreams must be on image mime type. (@abollini will update.) * * @param context the DSpace context * @param item the DSpace Item @@ -93,12 +105,6 @@ public class SequenceService extends AbstractResourceService { } /** - * A link to an external resource intended for display or download by a human user. - * This property can be used to link from a manifest, collection or other resource - * to the preferred viewing environment for that resource, such as a viewer page on - * the publisher’s web site. Other uses include a rendering of a manifest as a PDF - * or EPUB. - * * This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds * it to the Sequence if found. * @@ -126,8 +132,7 @@ public class SequenceService extends AbstractResourceService { if (mimeType != null && mimeType.contentEquals("application/pdf")) { String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; sequenceGenerator.addRendering( - externalLinksGenerator - .setIdentifier(id) + new ExternalLinksGenerator(id) .setLabel(PDF_DOWNLOAD_LABEL) .setFormat(mimeType) ); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java index b30a2ca14f..05676d1b79 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java @@ -42,11 +42,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; + /** - * Support for https://github.com/dbmdz/solr-ocrhighlighting + * This service provides methods for executing a solr search against the solr index. There should be a single + * instance of this service per request. The {@code @RequestScope} provides a single instance created and + * available during complete lifecycle of the HTTP request. + *

    + * https://github.com/dbmdz/solr-ocrhighlighting */ -@Component @RequestScope +@Component public class WordHighlightSolrSearch implements SearchAnnotationService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(WordHighlightSolrSearch.class); @@ -180,7 +185,7 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { JsonObject body = gson.fromJson(json, JsonObject.class); if (body == null) { log.warn("Unable to process json response."); - return utils.asJson(searchResult.getResource()); + return utils.asJson(searchResult.generate()); } // outer ocr highlight element JsonObject highs = body.getAsJsonObject("ocrHighlighting"); @@ -201,7 +206,7 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { } } - return utils.asJson(searchResult.getResource()); + return utils.asJson(searchResult.generate()); } /** @@ -253,11 +258,10 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { String annotationIdentifier = this.endpoint + uuid + "/annot/" + pageId + "-" + params; String canvasIdentifier = this.endpoint + uuid + "/canvas/" + pageId + "#xywh=" + params; contentAsText.setText(text); - CanvasGenerator canvas = new CanvasGenerator().setIdentifier(canvasIdentifier); + CanvasGenerator canvas = new CanvasGenerator(canvasIdentifier); - AnnotationGenerator annotationGenerator = new AnnotationGenerator() - .setMotivation(AnnotationGenerator.PAINTING) - .setIdentifier(annotationIdentifier) + AnnotationGenerator annotationGenerator = new AnnotationGenerator(annotationIdentifier, + AnnotationGenerator.PAINTING) .setOnCanvas(canvas) .setResource(contentAsText) .setWithin(getWithinManifest()); From a22fe26d2f650027f1363923e6b2fe339e235bec Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 26 Sep 2021 13:07:04 -0700 Subject: [PATCH 0332/1254] Additional documentation in iiif.cfg file. --- dspace/config/modules/iiif.cfg | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index 71a205d9b4..1dda1dfa14 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -1,17 +1,23 @@ #### IIIF CONFIGURATION #### -# Base URL of the DSpace iiif API endpoint. +# Base URL of the IIIF API endpoint. This is used when +# setting annotation identifiers. Some identifiers are used for +# linking and must reference a valid IIIF API endpoint. iiif.url = ${dspace.server.url}/iiif/ # Base URL of the IIIF image server. iiif.image.server = http://localhost:8182/iiif/2/ -# Base URL of the solr search index (IIIF Search API). -iiif.search.url = ${solr.server}/solr/word_highlighting +# Base URL of the search service used to support the optional IIIF Search +# capability. The actual path will depend on how search is supported. +# (experimental) +# iiif.search.url = ${solr.server}/solr/word_highlighting -# This is the search service under development currently. -iiif.search.plugin = WordHighlightSolrSearch +# The search plugin used to support IIIF Search. (experimental) +# iiif.search.plugin = org.dspace.app.rest.iiif.service.WordHighlightSolrSearch -# Required path prefix for bitstream content. +# Required path prefix for bitstream content. This is used for creating +# the path for retrieving bitstreams for "rendering" +# and "seeAlso" annotations via the DSpace API. iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams # Sets the viewing hint. Possible values: "paged" or "individuals". From 84ef661cfbf1e0ba82a7ccd3b51198d9a879e4e2 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sun, 26 Sep 2021 13:07:50 -0700 Subject: [PATCH 0333/1254] Updated experimentatal search plugin support to use full package name. --- .../dspace/app/rest/iiif/service/WordHighlightSolrSearch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java index 05676d1b79..ff6a08df65 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java @@ -77,7 +77,7 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { @Override public boolean getSearchPlugin(String className) { - return className.contentEquals(WordHighlightSolrSearch.class.getSimpleName()); + return className.contentEquals(WordHighlightSolrSearch.class.getCanonicalName()); } /** From 21032f66a5d41a5b7a793af7a9f4aed90f7f6414 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sun, 26 Sep 2021 22:59:38 +0200 Subject: [PATCH 0334/1254] Finalize configuration via metadata refactoring and hierarchical range support --- .../src/main/resources/Messages.properties | 11 + .../java/org/dspace/builder/ItemBuilder.java | 4 + .../iiif/model/generator/CanvasGenerator.java | 20 + .../model/generator/CanvasItemsGenerator.java | 9 +- .../model/generator/ManifestGenerator.java | 5 +- .../generator/MetadataEntryGenerator.java | 3 +- .../iiif/service/CanvasLookupService.java | 4 +- .../app/rest/iiif/service/CanvasService.java | 81 +++- .../rest/iiif/service/ManifestService.java | 71 ++-- .../rest/iiif/service/SequenceService.java | 61 ++- .../BitstreamBytesIIIFVirtualMetadata.java | 31 ++ .../BitstreamChecksumIIIFVirtualMetadata.java | 30 ++ .../BitstreamFormatIIIFVirtualMetadata.java | 35 ++ .../util/BitstreamIIIFVirtualMetadata.java | 26 ++ .../BitstreamMimetypeIIIFVirtualMetadata.java | 35 ++ .../app/rest/iiif/service/util/IIIFUtils.java | 207 ++++++++-- .../app/rest/iiif/IIIFControllerIT.java | 391 +++++++++++++++++- dspace/config/modules/iiif.cfg | 11 +- 18 files changed, 913 insertions(+), 122 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamBytesIIIFVirtualMetadata.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamChecksumIIIFVirtualMetadata.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamFormatIIIFVirtualMetadata.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamIIIFVirtualMetadata.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamMimetypeIIIFVirtualMetadata.java diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index c7dec36655..0583fb493c 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -6,6 +6,9 @@ # http://www.dspace.org/license/ # +iiif.canvas.default-naming = Page +iiif.toc.root-label = Table of Contents + itemlist.dc.contributor.* = Author(s) itemlist.dc.contributor.author = Author(s) itemlist.dc.creator = Author(s) @@ -38,6 +41,14 @@ metadata.dc.relation.ispartofseries = Series/Report no. metadata.dc.subject = Keywords metadata.dc.title = Title metadata.dc.title.alternative = Other Titles +metadata.bitstream.dc.title = File name +metadata.bitstream.dc.description = Description +metadata.bitstream.iiif.image.width = Image Width (px) +metadata.bitstream.iiif.image.height= Image Height (px) +metadata.bitstream.iiif-virtual.format = Format +metadata.bitstream.iiif-virtual.mimetype = Mime Type +metadata.bitstream.iiif-virtual.bytes = File size +metadata.bitstream.iiif-virtual.checksum = Checksum org.dspace.app.itemexport.no-result = The DSpaceObject that you specified has no items. org.dspace.checker.ResultsLogger.bitstream-format = Bitstream format 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 dd0083f3ba..365ca57889 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -127,6 +127,10 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { return addMetadataValue(item, "iiif", "search", "enabled", "true"); } + public ItemBuilder withIIIFViewingHint(String hint) { + return addMetadataValue(item, "iiif", "viewing", "hint", hint); + } + public ItemBuilder withIIIFCanvasNaming(String naming) { return addMetadataValue(item, "iiif", "canvas", "naming", naming); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index 808d7bded1..5df2374754 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.List; import de.digitalcollections.iiif.model.ImageContent; +import de.digitalcollections.iiif.model.MetadataEntry; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; @@ -29,6 +30,8 @@ public class CanvasGenerator implements IIIFResource { private List images = new ArrayList(); private ImageContent thumbnail; + private final List metadata = new ArrayList<>(); + public CanvasGenerator setIdentifier(String identifier) { this.identifier = identifier; return this; @@ -83,6 +86,18 @@ public class CanvasGenerator implements IIIFResource { return this; } + /** + * Adds single metadata field to Manifest. + * @param field property field + * @param value property value + */ + public void addMetadata(String field, String value, String... rest) { + MetadataEntryGenerator metadataEntryGenerator = new MetadataEntryGenerator(); + metadataEntryGenerator.setField(field); + metadataEntryGenerator.setValue(value, rest); + metadata.add(metadataEntryGenerator.getValue()); + } + /** * Returns the canvas. * @return canvas model @@ -114,6 +129,11 @@ public class CanvasGenerator implements IIIFResource { canvas.addThumbnail(thumbnail); } } + if (metadata.size() > 0) { + for (MetadataEntry meta : metadata) { + canvas.addMetadata(meta); + } + } return canvas; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java index 869f3b88c0..cdf2074713 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java @@ -32,7 +32,7 @@ import org.springframework.web.context.annotation.RequestScope; public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { private String identifier; - private OtherContent rendering; + private final List renderings = new ArrayList<>(); private final List canvas = new ArrayList<>(); @Autowired @@ -53,8 +53,7 @@ public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.gene * @param otherContent wrapper for OtherContent */ public void addRendering(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { - - this.rendering = (OtherContent) otherContent.getResource(); + this.renderings.add((OtherContent) otherContent.getResource()); } /** @@ -70,8 +69,8 @@ public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.gene @Override public Resource getResource() { Sequence items = new Sequence(identifier); - if (rendering != null) { - items.addRendering(rendering); + for (OtherContent r : renderings) { + items.addRendering(r); } items.setCanvases(canvas); return items; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index b94d236993..4cdede08ba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -31,6 +31,9 @@ import org.springframework.web.context.annotation.RequestScope; * such as a title and other descriptive information about the object or the intellectual work that * it conveys. Each manifest describes how to present a single object such as a book, a photograph, * or a statue. + * + * Please note that this is a request scoped bean. This mean that for each http request a + * different instance will be initialized by Spring and used to serve this specific request. */ @Component @RequestScope @@ -48,7 +51,7 @@ public class ManifestGenerator implements IIIFResource { private ContentSearchService searchService; private final List license = new ArrayList<>(); private final List metadata = new ArrayList<>(); - private List ranges = new ArrayList<>(); + private final List ranges = new ArrayList<>(); @Autowired MetadataEntryGenerator metadataEntryGenerator; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java index 0cad9b0163..d46c5f30b1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.MetadataEntry; import de.digitalcollections.iiif.model.PropertyValue; +import org.dspace.core.I18nUtil; import org.springframework.stereotype.Component; @Component @@ -43,6 +44,6 @@ public class MetadataEntryGenerator implements IIIFValue { } else { metadataValues = new PropertyValue(value); } - return new MetadataEntry(new PropertyValue(field), metadataValues); + return new MetadataEntry(new PropertyValue(I18nUtil.getMessage("metadata." + field)), metadataValues); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java index 9a83cdf0d2..efaaa66df6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -46,8 +46,8 @@ public class CanvasLookupService extends AbstractResourceService { String mimeType = utils.getBitstreamMimeType(bitstream, context); CanvasGenerator canvasGenerator; try { - canvasGenerator = canvasService.getCanvas(item.getID().toString(), bitstream, bitstream.getBundles().get(0), - item, canvasPosition, mimeType); + canvasGenerator = canvasService.getCanvas(context, item.getID().toString(), bitstream, + bitstream.getBundles().get(0), item, canvasPosition, mimeType); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index 60753ed3b8..4f082e239c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -7,17 +7,27 @@ */ package org.dspace.app.rest.iiif.service; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; +import org.dspace.app.rest.iiif.service.util.BitstreamIIIFVirtualMetadata; import org.dspace.app.rest.iiif.service.util.IIIFUtils; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.dspace.core.I18nUtil; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -33,6 +43,11 @@ public class CanvasService extends AbstractResourceService { @Autowired IIIFUtils utils; + @Autowired + ApplicationContext applicationContext; + + protected String[] BITSTREAM_METADATA_FIELDS; + /** * Constructor. * @@ -40,6 +55,7 @@ public class CanvasService extends AbstractResourceService { */ public CanvasService(ConfigurationService configurationService) { setConfiguration(configurationService); + BITSTREAM_METADATA_FIELDS = configurationService.getArrayProperty("iiif.metadata.bitstream"); } /** @@ -50,6 +66,7 @@ public class CanvasService extends AbstractResourceService { * Note that info.json is going to be replaced with metadata in the bitstream * DSO. * + * @param context DSpace Context * @param manifestId manifest id * @param bitstreamId uuid of the bitstream * @param mimeType the mimetype of the bitstream @@ -57,11 +74,11 @@ public class CanvasService extends AbstractResourceService { * @param count the canvas position in the sequence. * @return canvas object */ - protected CanvasGenerator getCanvas(String manifestId, Bitstream bitstream, Bundle bundle, Item item, int count, - String mimeType) { + protected CanvasGenerator getCanvas(Context context, String manifestId, Bitstream bitstream, Bundle bundle, + Item item, int count, String mimeType) { int pagePosition = count + 1; - String canvasNaming = utils.getCanvasNaming(item, "Page"); + String canvasNaming = utils.getCanvasNaming(item, I18nUtil.getMessage("iiif.canvas.default-naming")); String label = utils.getIIIFLabel(bitstream, canvasNaming + " " + pagePosition); int canvasWidth = utils.getCanvasWidth(bitstream, bundle, item, DEFAULT_CANVAS_WIDTH); int canvasHeight = utils.getCanvasHeight(bitstream, bundle, item, DEFAULT_CANVAS_HEIGHT); @@ -73,9 +90,10 @@ public class CanvasService extends AbstractResourceService { ImageContentGenerator thumb = imageContentService.getImageContent(bitstreamId, mimeType, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); - return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) - .addImage(image.getResource()).addThumbnail(thumb.getResource()).setHeight(canvasHeight) - .setWidth(canvasWidth).setLabel(label); + return addMetadata(context, bitstream, + new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) + .addImage(image.getResource()).addThumbnail(thumb.getResource()).setHeight(canvasHeight) + .setWidth(canvasWidth).setLabel(label)); } /** @@ -89,4 +107,55 @@ public class CanvasService extends AbstractResourceService { return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + identifier + startCanvas); } + /** + * Adds DSpace bitstream metadata to the canvas. + * + * @param context the DSpace Context + * @param item the DSpace item + */ + private CanvasGenerator addMetadata(Context context, Bitstream bitstream, CanvasGenerator canvasGenerator) { + BitstreamService bService = ContentServiceFactory.getInstance().getBitstreamService(); + for (String field : BITSTREAM_METADATA_FIELDS) { + if (StringUtils.startsWith(field, "@") && StringUtils.endsWith(field, "@")) { + String virtualFieldName = field.substring(1, field.length() - 1); + String beanName = BitstreamIIIFVirtualMetadata.IIIF_BITSTREAM_VIRTUAL_METADATA_BEAN_PREFIX + + virtualFieldName; + BitstreamIIIFVirtualMetadata virtual = applicationContext.getBean(beanName, + BitstreamIIIFVirtualMetadata.class); + List values = virtual.getValues(context, bitstream); + if (values.size() > 0) { + if (values.size() > 1) { + canvasGenerator.addMetadata("bitstream.iiif-virtual." + virtualFieldName, values.get(0), + values.subList(1, values.size()).toArray(new String[values.size() - 1])); + } else { + canvasGenerator.addMetadata("bitstream.iiif-virtual." + virtualFieldName, values.get(0)); + } + } + } else { + String[] eq = field.split("\\."); + String schema = eq[0]; + String element = eq[1]; + String qualifier = null; + if (eq.length > 2) { + qualifier = eq[2]; + } + List metadata = bService.getMetadata(bitstream, schema, element, qualifier, + Item.ANY); + List values = new ArrayList(); + for (MetadataValue meta : metadata) { + values.add(meta.getValue()); + } + if (values.size() > 0) { + if (values.size() > 1) { + canvasGenerator.addMetadata("bitstream." + field, values.get(0), + values.subList(1, values.size()).toArray(new String[values.size() - 1])); + } else { + canvasGenerator.addMetadata("bitstream." + field, values.get(0)); + } + } + } + } + return canvasGenerator; + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 57e021d0c4..d68d0978ba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.iiif.service; import java.sql.SQLException; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -28,13 +28,22 @@ import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.core.I18nUtil; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * Generates IIIF Manifest JSON response for a DSpace Item. + * Generates IIIF Manifest JSON response for a DSpace Item. Please note that + * this is a request scoped bean. This mean that for each http request a + * different instance will be initialized by Spring and used to serve this + * specific request. This is needed because some configuration are cached in the + * instance. Moreover, many injected dependencies are also request scoped or + * prototype (that will turn in a request scope when injected in a request scope + * bean). The generators need to be request scoped as they act as builder + * storing the object state during each incremental building step until the + * final object is returned (IIIF Resource) */ @Component @RequestScope @@ -105,9 +114,6 @@ public class ManifestService extends AbstractResourceService { * @return manifest object */ private void populateManifest(Item item, Context context) { - // If an IIIF bundle is found it will be used. Otherwise, - // images in the ORIGINAL bundle will be used. - List bundles = utils.getIiifBundles(item); String manifestId = getManifestId(item.getID()); manifestGenerator.setIdentifier(manifestId); manifestGenerator.setLabel(item.getName()); @@ -117,20 +123,35 @@ public class ManifestService extends AbstractResourceService { addMetadata(context, item); addViewingHint(item); addThumbnail(item, context); + addRanges(context, item, manifestId); + manifestGenerator.addSequence( + sequenceService.getSequence(item, context)); + addSeeAlso(item); + } + /** + * Add the ranges to the manifest structure. Ranges are generated from the + * iiif.toc metadata + * + * @param context the DSpace Context + * @param item the DSpace Item to represent + * @param manifestId the generated manifestId + */ + private void addRanges(Context context, Item item, String manifestId) { + List bundles = utils.getIIIFBundles(item); RangeGenerator root = new RangeGenerator(rangeService); - root.setLabel("Table of Contents"); + root.setLabel(I18nUtil.getMessage("iiif.toc.root-label")); root.setIdentifier(manifestId + "/range/r0"); - manifestGenerator.addRange(root); +// manifestGenerator.addRange(root); - Map tocRanges = new HashMap(); + Map tocRanges = new LinkedHashMap(); for (Bundle bnd : bundles) { String bundleToCPrefix = null; if (bundles.size() > 1) { - bundleToCPrefix = utils.getIIIFFirstToC(bnd); + bundleToCPrefix = utils.getBundleIIIFToC(bnd); } RangeGenerator lastRange = root; - for (Bitstream b : utils.getIiifBitstreams(context, bnd)) { + for (Bitstream b : utils.getIIIFBitstreams(context, bnd)) { CanvasGenerator canvasId = sequenceService.addCanvas(context, item, bnd, b); List tocs = utils.getIIIFToCs(b, bundleToCPrefix); if (tocs.size() > 0) { @@ -153,7 +174,7 @@ public class ManifestService extends AbstractResourceService { currRange.addSubRange(range); // add the range to the manifest - manifestGenerator.addRange(range); +// manifestGenerator.addRange(range); // move the current range currRange = range; @@ -170,10 +191,12 @@ public class ManifestService extends AbstractResourceService { } } } - manifestGenerator.addSequence( - sequenceService.getSequence(item, context)); - - addSeeAlso(item); + if (tocRanges.size() > 0) { + manifestGenerator.addRange(root); + for (RangeGenerator range : tocRanges.values()) { + manifestGenerator.addRange(range); + } + } } /** @@ -216,15 +239,15 @@ public class ManifestService extends AbstractResourceService { manifestGenerator.addMetadata(field, values.get(0)); } } - String descrValue = item.getItemService().getMetadataFirstValue(item, "dc", "description", null, Item.ANY); - if (StringUtils.isNotBlank(descrValue)) { - manifestGenerator.addDescription(descrValue); - } + } + String descrValue = item.getItemService().getMetadataFirstValue(item, "dc", "description", null, Item.ANY); + if (StringUtils.isNotBlank(descrValue)) { + manifestGenerator.addDescription(descrValue); + } - String licenseUriValue = item.getItemService().getMetadataFirstValue(item, "dc", "rights", "uri", Item.ANY); - if (StringUtils.isNotBlank(licenseUriValue)) { - manifestGenerator.addLicense(licenseUriValue); - } + String licenseUriValue = item.getItemService().getMetadataFirstValue(item, "dc", "rights", "uri", Item.ANY); + if (StringUtils.isNotBlank(licenseUriValue)) { + manifestGenerator.addLicense(licenseUriValue); } } @@ -288,7 +311,7 @@ public class ManifestService extends AbstractResourceService { * @param context DSpace context */ private void addThumbnail(Item item, Context context) { - List bitstreams = utils.getIiifBitstreams(context, item); + List bitstreams = utils.getIIIFBitstreams(context, item); if (bitstreams != null && bitstreams.size() > 0) { String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context); ImageContentGenerator image = imageContentService diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java index e2e140cec1..048460f8cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.iiif.service; import java.sql.SQLException; import java.util.List; -import java.util.UUID; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; @@ -26,13 +25,10 @@ import org.springframework.stereotype.Component; @Component @Scope("prototype") -public class SequenceService extends AbstractResourceService { +public class SequenceService extends AbstractResourceService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SequenceService.class); - // TODO i18n - private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; - /* * The counter tracks the position of the bitstream in the list and is used to create the canvas identifier. * The order of bitstreams (and thus page order in documents) is determined by position in the DSpace @@ -49,7 +45,6 @@ public class SequenceService extends AbstractResourceService { @Autowired CanvasService canvasService; - public SequenceService(ConfigurationService configurationService) { setConfiguration(configurationService); } @@ -71,12 +66,11 @@ public class SequenceService extends AbstractResourceService { * @param bitstreams list of DSpace bitstreams */ public CanvasGenerator addCanvas(Context context, Item item, Bundle bnd, Bitstream bitstream) { - UUID bitstreamId = bitstream.getID(); String mimeType = utils.getBitstreamMimeType(bitstream, context); String manifestId = item.getID().toString(); CanvasGenerator canvasGenerator = - canvasService.getCanvas(manifestId, bitstream, bnd, item, counter, mimeType); - String canvasIdentifier = sequenceGenerator.addCanvas(canvasGenerator); + canvasService.getCanvas(context, manifestId, bitstream, bnd, item, counter, mimeType); + sequenceGenerator.addCanvas(canvasGenerator); counter++; return canvasGenerator; } @@ -95,32 +89,31 @@ public class SequenceService extends AbstractResourceService { * @param context DSpace context */ private void addRendering(Item item, Context context) { - List bundles = item.getBundles("ORIGINAL"); - if (bundles.size() == 0) { - return; - } - Bundle bundle = bundles.get(0); - List bitstreams = bundle.getBitstreams(); - for (Bitstream bitstream : bitstreams) { - String mimeType = null; - try { - mimeType = bitstream.getFormat(context).getMIMEType(); - } catch (SQLException e) { - e.printStackTrace(); - } - // If the ORIGINAL bundle contains a PDF, assume that it represents the - // item and add to rendering. Ignore other mime-types. Other options - // might be using the primary bitstream or relying on a bitstream metadata - // field, e.g. iiif.rendering - if (mimeType != null && mimeType.contentEquals("application/pdf")) { - String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; - sequenceGenerator.addRendering( - externalLinksGenerator - .setIdentifier(id) - .setLabel(PDF_DOWNLOAD_LABEL) - .setFormat(mimeType) - ); + List bundles = utils.getIIIFBundles(item); + for (Bundle bundle : bundles) { + List bitstreams = bundle.getBitstreams(); + for (Bitstream bitstream : bitstreams) { + String mimeType = null; + try { + mimeType = bitstream.getFormat(context).getMIMEType(); + } catch (SQLException e) { + e.printStackTrace(); + } + // If the bundle contains a PDF, assume that it represents the + // item and add to rendering. Ignore other mime-types. Other options + // might be using the primary bitstream or relying on a bitstream metadata + // field, e.g. iiif.rendering + if (mimeType != null && mimeType.contentEquals("application/pdf")) { + String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; + sequenceGenerator.addRendering( + externalLinksGenerator + .setIdentifier(id) + .setLabel(utils.getIIIFLabel(bitstream, bitstream.getName())) + .setFormat(mimeType) + ); + } } } } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamBytesIIIFVirtualMetadata.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamBytesIIIFVirtualMetadata.java new file mode 100644 index 0000000000..23b7b9ffd5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamBytesIIIFVirtualMetadata.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.app.rest.iiif.service.util; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.dspace.content.Bitstream; +import org.dspace.core.Context; +import org.springframework.stereotype.Component; + +/** + * Expose the Bitstream file size as a IIIF Metadata + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(BitstreamIIIFVirtualMetadata.IIIF_BITSTREAM_VIRTUAL_METADATA_BEAN_PREFIX + "bytes") +public class BitstreamBytesIIIFVirtualMetadata implements BitstreamIIIFVirtualMetadata { + + @Override + public List getValues(Context context, Bitstream bitstream) { + return Collections.singletonList(FileUtils.byteCountToDisplaySize(bitstream.getSizeBytes())); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamChecksumIIIFVirtualMetadata.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamChecksumIIIFVirtualMetadata.java new file mode 100644 index 0000000000..f933d386f2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamChecksumIIIFVirtualMetadata.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.iiif.service.util; + +import java.util.Collections; +import java.util.List; + +import org.dspace.content.Bitstream; +import org.dspace.core.Context; +import org.springframework.stereotype.Component; + +/** + * Expose the Bitstream Checksum as a IIIF Metadata + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(BitstreamIIIFVirtualMetadata.IIIF_BITSTREAM_VIRTUAL_METADATA_BEAN_PREFIX + "checksum") +public class BitstreamChecksumIIIFVirtualMetadata implements BitstreamIIIFVirtualMetadata { + + @Override + public List getValues(Context context, Bitstream bitstream) { + return Collections.singletonList(bitstream.getChecksum() + " (" + bitstream.getChecksumAlgorithm() + ")"); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamFormatIIIFVirtualMetadata.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamFormatIIIFVirtualMetadata.java new file mode 100644 index 0000000000..4ae1f6f0ff --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamFormatIIIFVirtualMetadata.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.iiif.service.util; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; + +import org.dspace.content.Bitstream; +import org.dspace.core.Context; +import org.springframework.stereotype.Component; + +/** + * Expose the Bitstream Format as a IIIF Metadata + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(BitstreamIIIFVirtualMetadata.IIIF_BITSTREAM_VIRTUAL_METADATA_BEAN_PREFIX + "format") +public class BitstreamFormatIIIFVirtualMetadata implements BitstreamIIIFVirtualMetadata { + + @Override + public List getValues(Context context, Bitstream bitstream) { + try { + return Collections.singletonList(bitstream.getFormatDescription(context)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamIIIFVirtualMetadata.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamIIIFVirtualMetadata.java new file mode 100644 index 0000000000..758ab916c0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamIIIFVirtualMetadata.java @@ -0,0 +1,26 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.iiif.service.util; + +import java.util.List; + +import org.dspace.content.Bitstream; +import org.dspace.core.Context; + +/** + * Interface to implement to expose additional information at the canvas level + * for the bitstream + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +public interface BitstreamIIIFVirtualMetadata { + public final String IIIF_BITSTREAM_VIRTUAL_METADATA_BEAN_PREFIX = "iiif.bitstream."; + + List getValues(Context context, Bitstream bitstream); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamMimetypeIIIFVirtualMetadata.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamMimetypeIIIFVirtualMetadata.java new file mode 100644 index 0000000000..d147570660 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamMimetypeIIIFVirtualMetadata.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.iiif.service.util; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; + +import org.dspace.content.Bitstream; +import org.dspace.core.Context; +import org.springframework.stereotype.Component; + +/** + * Expose the Bitstream format mime type as a IIIF Metadata + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * + */ +@Component(BitstreamIIIFVirtualMetadata.IIIF_BITSTREAM_VIRTUAL_METADATA_BEAN_PREFIX + "mimetype") +public class BitstreamMimetypeIIIFVirtualMetadata implements BitstreamIIIFVirtualMetadata { + + @Override + public List getValues(Context context, Bitstream bitstream) { + try { + return Collections.singletonList(bitstream.getFormat(context).getMIMEType()); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 2350512677..b07a85000d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -38,20 +38,34 @@ public class IIIFUtils { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFUtils.class); + // The DSpace bundle for other content related to item. + protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; + // The canvas position will be appended to this string. private static final String CANVAS_PATH_BASE = "/canvas/c"; + // metadata used to enable the iiif features on the item public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; + // metadata used to enable the iiif search service on the item public static final String METADATA_IIIF_SEARCH_ENABLED = "iiif.search.enabled"; + // metadata used to override the title/name exposed as label to iiif client public static final String METADATA_IIIF_LABEL = "iiif.label"; + // metadata used to override the description/abstract exposed as label to iiif client public static final String METADATA_IIIF_DESCRIPTION = "iiif.description"; + // metadata used to set the position of the resource in the iiif manifest structure public static final String METADATA_IIIF_TOC = "iiif.toc"; + // metadata used to set the naming convention (prefix) used for all canvas that has not an explicit name public static final String METADATA_IIIF_CANVAS_NAMING = "iiif.canvas.naming"; + // metadata used to set the iiif viewing hint public static final String METADATA_IIIF_VIEWING_HINT = "iiif.viewing.hint"; + // metadata used to set the width of the canvas that has not an explicit name public static final String METADATA_IMAGE_WIDTH = "iiif.image.width"; + // metadata used to set the height of the canvas that has not an explicit name public static final String METADATA_IMAGE_HEIGTH = "iiif.image.height"; + // string used in the metadata toc as separator among the different levels public static final String TOC_SEPARATOR = "|||"; + // convenient constant to split a toc in its components public static final String TOC_SEPARATOR_REGEX = "\\|\\|\\|"; // get module subclass. @@ -69,7 +83,7 @@ public class IIIFUtils { * * @return list of DSpace bundles with IIIF content */ - public List getIiifBundles(Item item) { + public List getIIIFBundles(Item item) { boolean iiif = isIIIFEnabled(item); List bundles = new ArrayList<>(); if (iiif) { @@ -78,6 +92,12 @@ public class IIIFUtils { return bundles; } + /** + * This method verify if the IIIF feature is enabled on the item + * + * @param item the dspace item + * @return true if the item supports IIIF + */ public boolean isIIIFEnabled(Item item) { return item.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) @@ -85,6 +105,13 @@ public class IIIFUtils { m.getValue().equalsIgnoreCase("yes")); } + /** + * Utility method to check is a bundle can contain bitstreams to use as IIIF + * resources + * + * @param b the DSpace bundle to check + * @return true if the bundle can contain bitstreams to use as IIIF resources + */ private boolean isIIIFBundle(Bundle b) { return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", @@ -94,21 +121,43 @@ public class IIIFUtils { .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); } - public List getIiifBitstreams(Context context, Item item) { + /** + * Return all the bitstreams in the item to be used as IIIF resources + * + * @param context the DSpace Context + * @param item the DSpace item + * @return a not null list of bitstreams to use as IIIF resources in the + * manifest + */ + public List getIIIFBitstreams(Context context, Item item) { List bitstreams = new ArrayList(); - for (Bundle bnd : getIiifBundles(item)) { + for (Bundle bnd : getIIIFBundles(item)) { bitstreams - .addAll(getIiifBitstreams(context, bnd)); + .addAll(getIIIFBitstreams(context, bnd)); } return bitstreams; } - public List getIiifBitstreams(Context context, Bundle bundle) { - return bundle.getBitstreams().stream().filter(b -> isIiifBitstream(context, b)) + /** + * Return all the bitstreams in the bundle to be used as IIIF resources + * + * @param context the DSpace Context + * @param item the DSpace Bundle + * @return a not null list of bitstreams to use as IIIF resources in the + * manifest + */ + public List getIIIFBitstreams(Context context, Bundle bundle) { + return bundle.getBitstreams().stream().filter(b -> isIIIFBitstream(context, b)) .collect(Collectors.toList()); } - private boolean isIiifBitstream(Context context, Bitstream b) { + /** + * Utility method to check is a bitstream can be used as IIIF resources + * + * @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) { 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")); @@ -116,8 +165,9 @@ public class IIIFUtils { /** * Returns the bitstream mime type + * * @param bitstream DSpace bitstream - * @param context DSpace context + * @param context DSpace context * @return mime type */ public String getBitstreamMimeType(Bitstream bitstream, Context context) { @@ -131,7 +181,9 @@ public class IIIFUtils { } /** - * Checks to see if the item is searchable. Based on the {@link #METADATA_IIIF_SEARCH_ENABLED} metadata. + * Checks to see if the item is searchable. Based on the + * {@link #METADATA_IIIF_SEARCH_ENABLED} metadata. + * * @param item DSpace item * @return true if the iiif search is enabled */ @@ -144,18 +196,16 @@ public class IIIFUtils { /** * Retrives a bitstream based on its position in the IIIF bundle. - * @param context DSpace Context - * @param item DSpace Item + * + * @param context DSpace Context + * @param item DSpace Item * @param canvasPosition bitstream position - * @return bitstream + * @return bitstream or null if the specified canvasPosition doesn't exist in + * the manifest */ public Bitstream getBitstreamForCanvas(Context context, Item item, int canvasPosition) { - List bitstreams = getIiifBitstreams(context, item); - try { - return bitstreams.size() > canvasPosition ? bitstreams.get(canvasPosition) : null; - } catch (RuntimeException e) { - throw new RuntimeException("The requested canvas is not available", e); - } + List bitstreams = getIIIFBitstreams(context, item); + return bitstreams.size() > canvasPosition ? bitstreams.get(canvasPosition) : null; } /** @@ -205,24 +255,67 @@ public class IIIFUtils { return false; } + /** + * Return all the bitstreams in the item to be used as annotations + * + * @param context the DSpace Context + * @param item the DSpace item + * @return a not null list of bitstreams to use as IIIF resources in the + * manifest + */ public List getSeeAlsoBitstreams(Item item) { - return new ArrayList(); + List seeAlsoBitstreams = new ArrayList(); + List bundles = item.getBundles(OTHER_CONTENT_BUNDLE); + if (bundles.size() > 0) { + for (Bundle bundle : bundles) { + List bitstreams = bundle.getBitstreams(); + seeAlsoBitstreams.addAll(bitstreams); + } + } + return seeAlsoBitstreams; } + /** + * Return the custom iiif label for the resource or the provided default if none + * + * @param dso the dspace object to use as iiif resource + * @param defaultLabel the default label to return if none is specified in the + * metadata + * @return the iiif label for the dspace object + */ public String getIIIFLabel(DSpaceObject dso, String defaultLabel) { return dso.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_LABEL)) .findFirst().map(m -> m.getValue()).orElse(defaultLabel); } + /** + * Return the custom iiif description for the resource or the provided default if none + * + * @param dso the dspace object to use as iiif resource + * @param defaultLabel the default description to return if none is specified in the + * metadata + * @return the iiif label for the dspace object + */ public String getIIIFDescription(DSpaceObject dso, String defaultDescription) { return dso.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_DESCRIPTION)) .findFirst().map(m -> m.getValue()).orElse(defaultDescription); } - public List getIIIFToCs(DSpaceObject dso, String prefix) { - List tocs = dso.getMetadata().stream() + /** + * Return the table of contents (toc) positions in the iiif structure where the + * resource appears. Please note that the same resource can belong to multiple + * ranges (i.e. a page that contains the last paragraph of a section and start + * the new section) + * + * @param bitstream the dspace bitstream + * @param prefix a string to add to all the returned toc inherited from the + * parent dspace object + * @return the iiif tocs for the dspace object + */ + public List getIIIFToCs(Bitstream bitstream, String prefix) { + List tocs = bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_TOC)) .map(m -> StringUtils.isNotBlank(prefix) ? prefix + TOC_SEPARATOR + m.getValue() : m.getValue()) .collect(Collectors.toList()); @@ -233,7 +326,13 @@ public class IIIFUtils { } } - public String getIIIFFirstToC(Bundle bundle) { + /** + * Return the iiif toc for the specified bundle + * + * @param bundle the dspace bundle + * @return the iiif toc for the specified bundle + */ + public String getBundleIIIFToC(Bundle bundle) { String label = bundle.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_LABEL)) .findFirst().map(m -> m.getValue()).orElse(bundle.getName()); @@ -242,40 +341,100 @@ public class IIIFUtils { .findFirst().map(m -> m.getValue() + TOC_SEPARATOR + label).orElse(label); } + /** + * Return the iiif viewing hint for the item + * + * @param item the dspace item + * @param defaultHint the default hint to apply if nothing else is defined at + * the item leve + * @return the iiif viewing hint for the item + */ public String getIIIFViewingHint(Item item, String defaultHint) { return item.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_VIEWING_HINT)) .findFirst().map(m -> m.getValue()).orElse(defaultHint); } + /** + * Return the width for the canvas associated with the bitstream. If the + * bitstream doesn't provide directly the information it is retrieved from the + * bundle, item or default. + * + * @param bitstream the dspace bitstream used in the canvas + * @param bundle the bundle the bitstream belong to + * @param item the item the bitstream belong to + * @param defaultWidth the default width to apply if no other preferences are + * found + * @return the width in pixel for the canvas associated with the bitstream + */ public int getCanvasWidth(Bitstream bitstream, Bundle bundle, Item item, int defaultWidth) { return getSizeFromMetadata(bitstream, METADATA_IMAGE_WIDTH, getSizeFromMetadata(bundle, METADATA_IMAGE_WIDTH, getSizeFromMetadata(item, METADATA_IMAGE_WIDTH, defaultWidth))); } + /** + * Return the height for the canvas associated with the bitstream. If the + * bitstream doesn't provide directly the information it is retrieved from the + * bundle, item or default. + * + * @param bitstream the dspace bitstream used in the canvas + * @param bundle the bundle the bitstream belong to + * @param item the item the bitstream belong to + * @param defaultHeight the default width to apply if no other preferences are + * found + * @return the height in pixel for the canvas associated with the bitstream + */ public int getCanvasHeight(Bitstream bitstream, Bundle bundle, Item item, int defaultHeight) { return getSizeFromMetadata(bitstream, METADATA_IMAGE_HEIGTH, getSizeFromMetadata(bundle, METADATA_IMAGE_HEIGTH, getSizeFromMetadata(item, METADATA_IMAGE_HEIGTH, defaultHeight))); } + /** + * Utility method to extract an integer from metadata value. The defaultValue is + * returned if there are not values for the specified metadata or the value is + * not a valid integer. Only the first metadata value if any is used + * + * @param dso the dspace object + * @param metadata the metadata key (schema.element[.qualifier] + * @param defaultValue default to return if the metadata value is not an integer + * @return an integer from metadata value + */ private int getSizeFromMetadata(DSpaceObject dso, String metadata, int defaultValue) { return dso.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(metadata)) .findFirst().map(m -> castToInt(m, defaultValue)).orElse(defaultValue); } - private int castToInt(MetadataValue m, int defaultWidth) { + /** + * Utility method to cast a metadata value to int. The defaultInt is returned if + * the metadata value is not a valid integer + * + * @param m the metadata value + * @param defaultIntthe default to return if the metadata value is not an + * integer + * @return an int corresponding to the metadata value + */ + private int castToInt(MetadataValue m, int defaultInt) { try { return Integer.parseInt(m.getValue()); } catch (NumberFormatException e) { log.error("Error parsing " + m.getMetadataField().toString('.') + " of " + m.getDSpaceObject().getID() + " the value " + m.getValue() + " is not an integer. Returning the default."); } - return defaultWidth; + return defaultInt; } + /** + * Return the prefix to use to generate canvas name for canvas that has no an + * explicit IIIF label + * + * @param item the DSpace Item + * @param defaultNaming a default to return if the item has not a custom value + * @return the prefix to use to generate canvas name for canvas that has no an + * explicit IIIF label + */ public String getCanvasNaming(Item item, String defaultNaming) { return item.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_CANVAS_NAMING)) 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 44522d3cca..a35b1cc524 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 @@ -69,7 +69,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { } @Test - public void findOneIIIFSearchableEntityTypeIT() throws Exception { + public void findOneIIIFSearchableEntityTypeWithGlobalConfigIT() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -97,7 +97,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { bitstream2 = BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) + .createBitstream(context, publicItem1, is) .withName("Bitstream2.png") .withMimeType("image/png") .build(); @@ -112,23 +112,42 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.service.profile", is("http://iiif.io/api/search/0/search"))) .andExpect(jsonPath("$.thumbnail.@id", Matchers.containsString("/iiif/2/" + bitstream1.getID()))) + .andExpect(jsonPath("$.metadata[0].label", is("Title"))) + .andExpect(jsonPath("$.metadata[0].value", is("Public item 1"))) + .andExpect(jsonPath("$.metadata[1].label", is("Issue Date"))) + .andExpect(jsonPath("$.metadata[1].value", is("2017-10-17"))) + .andExpect(jsonPath("$.metadata[2].label", is("Authors"))) + .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"))) .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(1200))) .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"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[0].value", is("Bitstream1.jpg"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[1].label", is("Format"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[1].value", is("JPEG"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[2].label", is("Mime Type"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[2].value", is("image/jpeg"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[3].label", is("File size"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[3].value", is("19 bytes"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[4].label", is("Checksum"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[4].value", + is("11e23c5702595ba512c1c2ee8e8d6153 (MD5)"))) .andExpect(jsonPath("$.sequences[0].canvases[1].@id", Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c1"))) .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Page 2"))) .andExpect(jsonPath("$.sequences[0].canvases[1].images[0].resource.service.@id", Matchers.endsWith(bitstream2.getID().toString()))) + .andExpect(jsonPath("$.structures").doesNotExist()) .andExpect(jsonPath("$.related.@id", Matchers.containsString("/items/" + publicItem1.getID()))); } @Test - public void findOneIIIFSearchableWithGlobalConfigIT() throws Exception { + public void findOneIIIFSearchableWithMixedConfigIT() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -142,19 +161,26 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .enableIIIF() .withIIIFCanvasWidth(2000) .withIIIFCanvasHeight(3000) - .withEntityType("IIIFSearchable") + .withIIIFCanvasNaming("Global") + .enableIIIFSearch() .build(); String bitstreamContent = "ThisIsSomeText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) + .createBitstream(context, publicItem1, is) .withName("Bitstream1.jpg") .withMimeType("image/jpeg") .withIIIFLabel("Custom Label") .withIIIFCanvasWidth(3163) .withIIIFCanvasHeight(4220) - //.withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") .build(); } context.restoreAuthSystemState(); @@ -164,14 +190,20 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) - .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(3163))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(4220))) + .andExpect(jsonPath("$.sequences[0].canvases[1].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c1"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Global 2"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].width", is(2000))) + .andExpect(jsonPath("$.sequences[0].canvases[1].height", is(3000))) + .andExpect(jsonPath("$.structures").doesNotExist()) .andExpect(jsonPath("$.service").exists()); } @Test - public void findOneIIIFSearchableWithInfoJsonIT() throws Exception { + public void findOneIIIFSearchableWithCustomBundleAndConfigIT() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -208,6 +240,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))) .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(3163))) .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(4220))) + .andExpect(jsonPath("$.structures").doesNotExist()) .andExpect(jsonPath("$.service").exists()); } @@ -227,6 +260,15 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .enableIIIF() .build(); + Item publicItem2 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 2") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withMetadata("dc", "rights", "uri", "https://license.org") + .withIIIFViewingHint("paged") + .enableIIIF() + .build(); + String bitstreamContent = "ThisIsSomeDummyText"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. @@ -234,6 +276,12 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withName("Bitstream1.jpg") .withMimeType("image/jpeg") .build(); + + BitstreamBuilder. + createBitstream(context, publicItem2, is, IIIFBundle) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .build(); } String bitstreamContent2 = "ThisIsSomeDummyText2"; @@ -243,6 +291,12 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withName("Bitstream2.png") .withMimeType("image/png") .build(); + + BitstreamBuilder. + createBitstream(context, publicItem2, is, IIIFBundle) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .build(); } String bitstreamContent3 = "ThisIsSomeDummyText3"; @@ -252,24 +306,32 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withName("Bitstream3.tiff") .withMimeType("image/tiff") .build(); + + BitstreamBuilder. + createBitstream(context, publicItem2, is, IIIFBundle) + .withName("Bitstream3.tiff") + .withMimeType("image/tiff") + .build(); } context.restoreAuthSystemState(); - // With more than 2 bitstreams in IIIF bundle, the sequence viewing hint should be "paged" - // unless that has been changed in dspace configuration. This test assumes that DSpace - // has been configured to return the "individuals" hint for documents to better support - // search results in Mirador. That is the current dspace.cfg default setting. + // The sequence viewing hint should be "individuals" in the item metadata. getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.license", is("https://license.org"))) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .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", Matchers.hasSize(3))) .andExpect(jsonPath("$.viewingHint", is("individuals"))) .andExpect(jsonPath("$.service").doesNotExist()); + getClient().perform(get("/iiif/" + publicItem2.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.license", is("https://license.org"))) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases", Matchers.hasSize(3))) + .andExpect(jsonPath("$.viewingHint", is("paged"))) + .andExpect(jsonPath("$.service").doesNotExist()); } @Test @@ -353,7 +415,274 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { } @Test - public void findOneIIIFEntityTypeIT() throws Exception { + public void findOneWithBundleStructures() 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") + .withIIIFCanvasHeight(3000) + .withIIIFCanvasWidth(2000) + .withIIIFCanvasNaming("Global") + .enableIIIF() + .enableIIIFSearch() + .build(); + + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream2.png") + .withMimeType("image/png") + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, publicItem1, is, IIIFBundle) + .withName("Bitstream3.tiff") + .withMimeType("image/tiff") + .build(); + } + + context.restoreAuthSystemState(); + // expect structures elements with label and canvas id. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) + .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Global 2"))) + .andExpect(jsonPath("$.sequences[0].canvases[2].label", is("Global 3"))) + .andExpect(jsonPath("$.structures[0].@id", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0"))) + .andExpect(jsonPath("$.structures[0].label", is("Table of Contents"))) + .andExpect(jsonPath("$.structures[0].ranges[0]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) + .andExpect(jsonPath("$.structures[0].ranges[1]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-1"))) + .andExpect(jsonPath("$.structures[1].@id", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) + .andExpect(jsonPath("$.structures[1].label", is("ORIGINAL"))) + .andExpect(jsonPath("$.structures[1].canvases[0]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.structures[2].@id", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-1"))) + .andExpect(jsonPath("$.structures[2].label", is("IIIF"))) + .andExpect(jsonPath("$.structures[2].canvases[0]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c1"))) + .andExpect(jsonPath("$.structures[2].canvases[1]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c2"))) + .andExpect(jsonPath("$.service").exists()); + } + + @Test + public void findOneWithHierarchicalStructures() 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() + .enableIIIFSearch() + .build(); + + String bitstreamContent = "ThisIsSomeText"; + Bitstream bitstream1 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .withIIIFToC("Section 1") + .build(); + } + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFToC("Section 1|||a") + .build(); + } + Bitstream bitstream3 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream3 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream3.jpg") + .withMimeType("image/jpeg") + .withIIIFToC("Section 1|||a") + .build(); + } + Bitstream bitstream4 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream4 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream4.jpg") + .withMimeType("image/jpeg") + .withIIIFToC("Section 1|||b") + .build(); + } + Bitstream bitstream5 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream5 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream5.jpg") + .withMimeType("image/jpeg") + .withIIIFToC("Section 1") + .build(); + } + Bitstream bitstream6 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream6 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream6.png") + .withMimeType("image/png") + .withIIIFToC("Section 2") + .build(); + } + Bitstream bitstream7 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream7 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream7.tiff") + .withMimeType("image/tiff") + .withIIIFToC("Section 2") + .build(); + } + Bitstream bitstream8 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream8 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream8.tiff") + .withMimeType("image/tiff") + .withIIIFToC("Section 2|||sub 2-1") + .build(); + } + + context.restoreAuthSystemState(); + // expect structures elements with label and canvas id. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases", Matchers.hasSize(8))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].images[0].resource.@id", + Matchers.containsString(bitstream1.getID().toString()))) + .andExpect(jsonPath("$.sequences[0].canvases[1].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c1"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].images[0].resource.@id", + Matchers.containsString(bitstream2.getID().toString()))) + .andExpect(jsonPath("$.sequences[0].canvases[2].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c2"))) + .andExpect(jsonPath("$.sequences[0].canvases[2].images[0].resource.@id", + Matchers.containsString(bitstream3.getID().toString()))) + .andExpect(jsonPath("$.sequences[0].canvases[3].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c3"))) + .andExpect(jsonPath("$.sequences[0].canvases[3].images[0].resource.@id", + Matchers.containsString(bitstream4.getID().toString()))) + .andExpect(jsonPath("$.sequences[0].canvases[4].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c4"))) + .andExpect(jsonPath("$.sequences[0].canvases[4].images[0].resource.@id", + Matchers.containsString(bitstream5.getID().toString()))) + .andExpect(jsonPath("$.sequences[0].canvases[5].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c5"))) + .andExpect(jsonPath("$.sequences[0].canvases[5].images[0].resource.@id", + Matchers.containsString(bitstream6.getID().toString()))) + .andExpect(jsonPath("$.sequences[0].canvases[6].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c6"))) + .andExpect(jsonPath("$.sequences[0].canvases[6].images[0].resource.@id", + Matchers.containsString(bitstream7.getID().toString()))) + .andExpect(jsonPath("$.sequences[0].canvases[7].@id", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c7"))) + .andExpect(jsonPath("$.sequences[0].canvases[7].images[0].resource.@id", + Matchers.containsString(bitstream8.getID().toString()))) + .andExpect(jsonPath("$.structures[0].@id", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0"))) + // the toc contains two top sections 1 & 2 without direct children canvases + .andExpect(jsonPath("$.structures[0].label", is("Table of Contents"))) + .andExpect(jsonPath("$.structures[0].ranges", Matchers.hasSize(2))) + .andExpect(jsonPath("$.structures[0].ranges[0]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) + .andExpect(jsonPath("$.structures[0].ranges[1]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-1"))) + .andExpect(jsonPath("$.structures[0].canvases").doesNotExist()) + // section 1 contains bitstream 1 and 5 and the sub section a and b + .andExpect(jsonPath("$.structures[1].@id", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) + .andExpect(jsonPath("$.structures[1].label", is("Section 1"))) + .andExpect(jsonPath("$.structures[1].ranges", Matchers.hasSize(2))) + .andExpect(jsonPath("$.structures[1].ranges[0]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0-0"))) + .andExpect(jsonPath("$.structures[1].ranges[1]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0-1"))) + .andExpect(jsonPath("$.structures[1].canvases", Matchers.hasSize(2))) + .andExpect(jsonPath("$.structures[1].canvases[0]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.structures[1].canvases[1]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c4"))) + // section 1 > a contains bitstream 2 and 3, no sub sections + .andExpect(jsonPath("$.structures[2].label", is("a"))) + .andExpect(jsonPath("$.structures[2].ranges").doesNotExist()) + .andExpect(jsonPath("$.structures[2].canvases", Matchers.hasSize(2))) + .andExpect(jsonPath("$.structures[2].canvases[0]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c1"))) + .andExpect(jsonPath("$.structures[2].canvases[1]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c2"))) + // section 1 > b contains only the bitstream 4 and no sub sections + .andExpect(jsonPath("$.structures[3].@id", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0-1"))) + .andExpect(jsonPath("$.structures[3].label", is("b"))) + .andExpect(jsonPath("$.structures[3].ranges").doesNotExist()) + .andExpect(jsonPath("$.structures[3].canvases", Matchers.hasSize(1))) + .andExpect(jsonPath("$.structures[3].canvases[0]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c3"))) + // section 2 contains bitstream 6 and 7, sub section "sub 2-1" + .andExpect(jsonPath("$.structures[4].label", is("Section 2"))) + .andExpect(jsonPath("$.structures[4].ranges", Matchers.hasSize(1))) + .andExpect(jsonPath("$.structures[4].ranges[0]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-1-0"))) + .andExpect(jsonPath("$.structures[4].canvases", Matchers.hasSize(2))) + .andExpect(jsonPath("$.structures[4].canvases[0]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c5"))) + .andExpect(jsonPath("$.structures[4].canvases[1]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c6"))) + // section 2 > sub 2-1 contains only the bitstream 8 no sub sections + .andExpect(jsonPath("$.structures[5].@id", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-1-0"))) + .andExpect(jsonPath("$.structures[5].label", is("sub 2-1"))) + .andExpect(jsonPath("$.structures[5].ranges").doesNotExist()) + .andExpect(jsonPath("$.structures[5].canvases", Matchers.hasSize(1))) + .andExpect(jsonPath("$.structures[5].canvases[0]", + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c7"))) + .andExpect(jsonPath("$.service").exists()); + } + + @Test + public void findOneIIIFNotSearcheableIT() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -463,14 +792,22 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { BitstreamBuilder. createBitstream(context, publicItem1, is) - .withName("Bitstream2.pdf") + .withName("Bitstream2.mp4") + .withMimeType("video/mp4") + .build(); + } + Bitstream pdf = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + pdf = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream3.pdf") .withMimeType("application/pdf") .build(); } context.restoreAuthSystemState(); - // Image in the ORIGINAL bundle added as canvas; PDF ignored... + // Image in the ORIGINAL bundle added as canvas; MP4 ignored, PDF offered as rendering... getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) @@ -478,6 +815,10 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .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].rendering.@id", + Matchers.endsWith(pdf.getID().toString() + "/content"))) + .andExpect(jsonPath("$.sequences[0].rendering.label", is("Bitstream3.pdf"))) + .andExpect(jsonPath("$.sequences[0].rendering.format", is("application/pdf"))) .andExpect(jsonPath("$.service").doesNotExist()); } @@ -511,6 +852,16 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { getClient().perform(get("/iiif/" + publicItem1.getID() + "/canvas/c0")) .andExpect(status().isOk()) .andExpect(jsonPath("$.@type", is("sc:Canvas"))) + .andExpect(jsonPath("$.metadata[0].label", is("File name"))) + .andExpect(jsonPath("$.metadata[0].value", is("IMG1.jpg"))) + .andExpect(jsonPath("$.metadata[1].label", is("Format"))) + .andExpect(jsonPath("$.metadata[1].value", is("JPEG"))) + .andExpect(jsonPath("$.metadata[2].label", is("Mime Type"))) + .andExpect(jsonPath("$.metadata[2].value", is("image/jpeg"))) + .andExpect(jsonPath("$.metadata[3].label", is("File size"))) + .andExpect(jsonPath("$.metadata[3].value", is("19 bytes"))) + .andExpect(jsonPath("$.metadata[4].label", is("Checksum"))) + .andExpect(jsonPath("$.metadata[4].value", is("11e23c5702595ba512c1c2ee8e8d6153 (MD5)"))) .andExpect(jsonPath("$.images[0].@type", is("oa:Annotation"))); } diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index 3fad86f2c7..fe3ce7c43a 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -44,16 +44,17 @@ iiif.attribution = ${dspace.name} iiif.logo.image = ${dspace.ui.url}/assets/images/dspace-logo.svg # (optional) one of individuals, paged or continuous. Can be overridden at the item level via # the iiif.view.hint metadata -iiif.document.viewing.hint = +iiif.document.viewing.hint = individuals -# ???? -iiif.url = + +# public base url of a iiif server able to serve DSpace images. The bitstream uuid is appended to this URL iiif.image.server = +# these should be not changed. They must match the routing configuration for the iiif controller +iiif.url = ${dspace.server.url}/iiif iiif.solr.search.url = -# ???? -iiif.bitstream.url = +iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams # default value for the canvas size. Can be overridden at the item, bundle or bitstream level # via the iiif.image.width e iiif.image.height metadata From b692b9e9ddb20e32f2c60e85006e624d6110647e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 24 Sep 2021 15:13:07 -0500 Subject: [PATCH 0335/1254] Ensure ItemDAOImpl queries are all ordered by UUID --- .../dspace/content/dao/impl/ItemDAOImpl.java | 33 ++++++++---- .../app/rest/CollectionRestRepositoryIT.java | 32 +++++++++--- .../dspace/app/rest/ItemRestRepositoryIT.java | 52 ++++++++++++------- 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java index 683a6502c5..fd404e2f3e 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java @@ -32,6 +32,7 @@ import org.dspace.eperson.EPerson; import org.hibernate.Criteria; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Property; import org.hibernate.criterion.Restrictions; @@ -54,14 +55,14 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findAll(Context context, boolean archived) throws SQLException { - Query query = createQuery(context, "FROM Item WHERE inArchive= :in_archive"); + Query query = createQuery(context, "FROM Item WHERE inArchive=:in_archive ORDER BY id"); query.setParameter("in_archive", archived); return iterate(query); } @Override public Iterator findAll(Context context, boolean archived, int limit, int offset) throws SQLException { - Query query = createQuery(context, "FROM Item WHERE inArchive= :in_archive"); + Query query = createQuery(context, "FROM Item WHERE inArchive=:in_archive ORDER BY id"); query.setParameter("in_archive", archived); query.setFirstResult(offset); query.setMaxResults(limit); @@ -71,7 +72,8 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findAll(Context context, boolean archived, boolean withdrawn) throws SQLException { - Query query = createQuery(context, "FROM Item WHERE inArchive= :in_archive or withdrawn = :withdrawn"); + Query query = createQuery(context, + "FROM Item WHERE inArchive=:in_archive or withdrawn=:withdrawn ORDER BY id"); query.setParameter("in_archive", archived); query.setParameter("withdrawn", withdrawn); return iterate(query); @@ -89,6 +91,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA if (lastModified != null) { queryStr.append(" AND last_modified > :last_modified"); } + queryStr.append(" ORDER BY i.id"); Query query = createQuery(context, queryStr.toString()); query.setParameter("in_archive", archived); @@ -102,7 +105,8 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findBySubmitter(Context context, EPerson eperson) throws SQLException { - Query query = createQuery(context, "FROM Item WHERE inArchive= :in_archive and submitter= :submitter"); + Query query = createQuery(context, + "FROM Item WHERE inArchive=:in_archive and submitter=:submitter ORDER BY id"); query.setParameter("in_archive", true); query.setParameter("submitter", eperson); return iterate(query); @@ -114,7 +118,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA if (!retrieveAllItems) { return findBySubmitter(context, eperson); } - Query query = createQuery(context, "FROM Item WHERE submitter= :submitter"); + Query query = createQuery(context, "FROM Item WHERE submitter=:submitter ORDER BY id"); query.setParameter("submitter", eperson); return iterate(query); } @@ -146,7 +150,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA if (value != null) { hqlQueryString += " AND STR(metadatavalue.value) = :text_value"; } - Query query = createQuery(context, hqlQueryString); + Query query = createQuery(context, hqlQueryString + " ORDER BY item.id"); query.setParameter("in_archive", inArchive); query.setParameter("metadata_field", metadataField); @@ -262,6 +266,8 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA criteria.add(Subqueries.notExists(subcriteria)); } } + criteria.addOrder(Order.asc("item.id")); + log.debug(String.format("Running custom query with %d filters", index)); return ((List) criteria.list()).iterator(); @@ -274,7 +280,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA Query query = createQuery(context, "SELECT item FROM Item as item join item.metadata metadatavalue " + "WHERE item.inArchive=:in_archive AND metadatavalue.metadataField = :metadata_field AND " + - "metadatavalue.authority = :authority"); + "metadatavalue.authority = :authority ORDER BY item.id"); query.setParameter("in_archive", inArchive); query.setParameter("metadata_field", metadataField); query.setParameter("authority", authority); @@ -286,7 +292,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA Integer offset) throws SQLException { Query query = createQuery(context, "select i from Item i join i.collections c " + - "WHERE :collection IN c AND i.inArchive=:in_archive"); + "WHERE :collection IN c AND i.inArchive=:in_archive ORDER BY i.id"); query.setParameter("collection", collection); query.setParameter("in_archive", true); if (offset != null) { @@ -309,6 +315,8 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA criteriaBuilder.notEqual(itemRoot.get(Item_.owningCollection), collection), criteriaBuilder.isMember(collection, itemRoot.get(Item_.collections)), criteriaBuilder.isTrue(itemRoot.get(Item_.inArchive)))); + criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get(Item_.id))); + criteriaQuery.groupBy(itemRoot.get(Item_.id)); return list(context, criteriaQuery, false, Item.class, limit, offset).iterator(); } @@ -327,7 +335,8 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findAllByCollection(Context context, Collection collection) throws SQLException { - Query query = createQuery(context, "select i from Item i join i.collections c WHERE :collection IN c"); + Query query = createQuery(context, + "select i from Item i join i.collections c WHERE :collection IN c ORDER BY i.id"); query.setParameter("collection", collection); return iterate(query); @@ -336,7 +345,8 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findAllByCollection(Context context, Collection collection, Integer limit, Integer offset) throws SQLException { - Query query = createQuery(context, "select i from Item i join i.collections c WHERE :collection IN c"); + Query query = createQuery(context, + "select i from Item i join i.collections c WHERE :collection IN c ORDER BY i.id"); query.setParameter("collection", collection); if (offset != null) { @@ -381,7 +391,8 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA @Override public Iterator findByLastModifiedSince(Context context, Date since) throws SQLException { - Query query = createQuery(context, "SELECT i FROM item i WHERE last_modified > :last_modified"); + Query query = createQuery(context, + "SELECT i FROM item i WHERE last_modified > :last_modified ORDER BY id"); query.setParameter("last_modified", since, TemporalType.TIMESTAMP); return iterate(query); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 09be02484f..a9e8dadbe7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -29,6 +29,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -2414,16 +2415,34 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .withName("Mapped Collection") .build(); + List items = new ArrayList(); + // This comparator is used to sort our test Items by java.util.UUID (which sorts them based on the RFC + // and not based on String comparison, see also https://stackoverflow.com/a/51031298/3750035 ) + Comparator compareByUUID = Comparator.comparing(i -> i.getID()); + Item item0 = ItemBuilder.createItem(context, collection).withTitle("Item 0").build(); + items.add(item0); Item item1 = ItemBuilder.createItem(context, collection).withTitle("Item 1").build(); + items.add(item1); Item item2 = ItemBuilder.createItem(context, collection).withTitle("Item 2").build(); + items.add(item2); Item item3 = ItemBuilder.createItem(context, collection).withTitle("Item 3").build(); + items.add(item3); Item item4 = ItemBuilder.createItem(context, collection).withTitle("Item 4").build(); + items.add(item4); Item item5 = ItemBuilder.createItem(context, collection).withTitle("Item 5").build(); + items.add(item5); Item item6 = ItemBuilder.createItem(context, collection).withTitle("Item 6").build(); + items.add(item6); Item item7 = ItemBuilder.createItem(context, collection).withTitle("Item 7").build(); + items.add(item7); Item item8 = ItemBuilder.createItem(context, collection).withTitle("Item 8").build(); + items.add(item8); Item item9 = ItemBuilder.createItem(context, collection).withTitle("Item 9").build(); + items.add(item9); + + // sort items list by UUID (as Items will come back ordered by UUID) + items.sort(compareByUUID); collectionService.addItem(context, mappedCollection, item0); collectionService.addItem(context, mappedCollection, item1); @@ -2445,12 +2464,13 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .param("embed.size", "mappedItems=5")) .andExpect(status().isOk()) .andExpect(jsonPath("$", CollectionMatcher.matchCollection(mappedCollection))) - .andExpect(jsonPath("$._embedded.mappedItems._embedded.mappedItems", Matchers.containsInAnyOrder( - ItemMatcher.matchItemProperties(item0), - ItemMatcher.matchItemProperties(item1), - ItemMatcher.matchItemProperties(item2), - ItemMatcher.matchItemProperties(item3), - ItemMatcher.matchItemProperties(item4) + .andExpect(jsonPath("$._embedded.mappedItems._embedded.mappedItems", + Matchers.containsInRelativeOrder( + ItemMatcher.matchItemProperties(items.get(0)), + ItemMatcher.matchItemProperties(items.get(1)), + ItemMatcher.matchItemProperties(items.get(2)), + ItemMatcher.matchItemProperties(items.get(3)), + ItemMatcher.matchItemProperties(items.get(4)) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/collections/" + mappedCollection.getID()))) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 1c47825a21..103eee48e2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -27,6 +27,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -111,6 +112,11 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + List items = new ArrayList(); + // This comparator is used to sort our test Items by java.util.UUID (which sorts them based on the RFC + // and not based on String comparison, see also https://stackoverflow.com/a/51031298/3750035 ) + Comparator compareByUUID = Comparator.comparing(i -> i.getID()); + //2. Three public items that are readable by Anonymous with different subjects Item publicItem1 = ItemBuilder.createItem(context, col1) .withTitle("Public item 1") @@ -118,6 +124,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withAuthor("Smith, Donald").withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); + items.add(publicItem1); Item publicItem2 = ItemBuilder.createItem(context, col2) .withTitle("Public item 2") @@ -125,6 +132,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withAuthor("Smith, Maria").withAuthor("Doe, Jane") .withSubject("TestingForMore").withSubject("ExtraEntry") .build(); + items.add(publicItem2); Item publicItem3 = ItemBuilder.createItem(context, col2) .withTitle("Public item 3") @@ -133,19 +141,19 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("AnotherTest").withSubject("TestingForMore") .withSubject("ExtraEntry") .build(); + items.add(publicItem3); + // sort items list by UUID (as Items will come back ordered by UUID) + items.sort(compareByUUID); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/core/items")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, - "Public item 1", "2017-10-17"), - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, - "Public item 2", "2016-02-13"), - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, - "Public item 3", "2016-02-13") + .andExpect(jsonPath("$._embedded.items", Matchers.containsInRelativeOrder( + ItemMatcher.matchItemProperties(items.get(0)), + ItemMatcher.matchItemProperties(items.get(1)), + ItemMatcher.matchItemProperties(items.get(2)) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items"))) @@ -185,6 +193,11 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withTemplateItem() .build(); + List items = new ArrayList(); + // This comparator is used to sort our test Items by java.util.UUID (which sorts them based on the RFC + // and not based on String comparison, see also https://stackoverflow.com/a/51031298/3750035 ) + Comparator compareByUUID = Comparator.comparing(i -> i.getID()); + //2. Three public items that are readable by Anonymous with different subjects Item publicItem1 = ItemBuilder.createItem(context, col1) .withTitle("Public item 1") @@ -192,6 +205,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withAuthor("Smith, Donald").withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); + items.add(publicItem1); Item publicItem2 = ItemBuilder.createItem(context, col2) .withTitle("Public item 2") @@ -199,6 +213,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withAuthor("Smith, Maria").withAuthor("Doe, Jane") .withSubject("TestingForMore").withSubject("ExtraEntry") .build(); + items.add(publicItem2); Item publicItem3 = ItemBuilder.createItem(context, col2) .withTitle("Public item 3") @@ -207,6 +222,9 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withSubject("AnotherTest").withSubject("TestingForMore") .withSubject("ExtraEntry") .build(); + items.add(publicItem3); + // sort items list by UUID (as Items will come back ordered by UUID) + items.sort(compareByUUID); // Create a Workspace Item (which in turn creates an Item with "in_archive=false") // This is only created to prove that WorkspaceItems are NOT counted/listed in this endpoint @@ -232,16 +250,13 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(token).perform(get("/api/core/items") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.items", Matchers.containsInAnyOrder( - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, - "Public item 1", "2017-10-17"), - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, - "Public item 2", "2016-02-13") + .andExpect(jsonPath("$._embedded.items", Matchers.containsInRelativeOrder( + ItemMatcher.matchItemProperties(items.get(0)), + ItemMatcher.matchItemProperties(items.get(1)) ))) .andExpect(jsonPath("$._embedded.items", Matchers.not( Matchers.contains( - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, - "Public item 3", "2016-02-13"), + ItemMatcher.matchItemProperties(items.get(2)), ItemMatcher.matchItemWithTitleAndDateIssued(itemInWorkspace, "In Progress Item", "2018-02-05"), ItemMatcher.matchItemWithTitleAndDateIssued(itemInWorkflow, @@ -271,15 +286,12 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .param("page", "1")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.items", Matchers.contains( - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, - "Public item 3", "2016-02-13") + ItemMatcher.matchItemProperties(items.get(2)) ))) .andExpect(jsonPath("$._embedded.items", Matchers.not( Matchers.contains( - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, - "Public item 1", "2017-10-17"), - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, - "Public item 2", "2016-02-13"), + ItemMatcher.matchItemProperties(items.get(0)), + ItemMatcher.matchItemProperties(items.get(1)), ItemMatcher.matchItemWithTitleAndDateIssued(itemInWorkspace, "In Progress Item", "2018-02-05"), ItemMatcher.matchItemWithTitleAndDateIssued(itemInWorkflow, From da4c5774e8f5e74fd646963109b7302685696d3a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 27 Sep 2021 10:24:59 -0700 Subject: [PATCH 0336/1254] Added not implemented exception for search service. --- .../exception/NotImplementedException.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/exception/NotImplementedException.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/exception/NotImplementedException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/exception/NotImplementedException.java new file mode 100644 index 0000000000..d8574ef4f9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/exception/NotImplementedException.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.iiif.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * This exception is used when the search service has not been implemented + * for this server. + */ +@ResponseStatus(value = HttpStatus.NOT_IMPLEMENTED, reason = "Method not implemented") +public class NotImplementedException extends RuntimeException { + + public NotImplementedException(String message) { + super(message); + } +} From fc3e68edbac6e29e5577739d8fdb208dd652bda9 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 27 Sep 2021 10:26:07 -0700 Subject: [PATCH 0337/1254] Search service update. --- .../app/rest/iiif/service/SearchService.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index b52ed8d53e..edaad00ea3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.UUID; import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.iiif.exception.NotImplementedException; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -39,21 +40,24 @@ public class SearchService extends AbstractResourceService { } /** - * Executes a search query for items in the current manifest. + * Executes a search query for items in the current manifest. A + * search plugin must be enabled. * * @param uuid dspace item uuid * @param query the solr query * @return IIIF search result with page coordinate annotations. */ - public String searchWithinManifest(UUID uuid, String query) { - for (SearchAnnotationService service : annotationService) { - if (service.getSearchPlugin(searchPlugin)) { - service.initializeQuerySettings(IIIF_ENDPOINT, getManifestId(uuid)); - return service.getSolrSearchResponse(uuid, query); + public String searchWithinManifest(UUID uuid, String query) throws NotImplementedException { + if (searchPlugin != null) { + for (SearchAnnotationService service : annotationService) { + if (service.getSearchPlugin(searchPlugin)) { + service.initializeQuerySettings(IIIF_ENDPOINT, getManifestId(uuid)); + return service.getSolrSearchResponse(uuid, query); + } } } - throw new RuntimeException( - "IIIF search plugin was not found." + throw new NotImplementedException( + "The IIIF search option is not enabled for this server." ); } From 42d82cf3223911153290490df82e6998bc163383 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 27 Sep 2021 10:26:26 -0700 Subject: [PATCH 0338/1254] Updated imports for style check. --- .../model/generator/AnnotationGenerator.java | 1 - .../generator/AnnotationListGenerator.java | 1 - .../model/generator/BehaviorGenerator.java | 1 - .../iiif/model/generator/CanvasGenerator.java | 1 - .../generator/ContentSearchGenerator.java | 1 - .../model/generator/ManifestGenerator.java | 1 - .../generator/MetadataEntryGenerator.java | 1 - .../iiif/model/generator/RangeGenerator.java | 1 - .../iiif/service/ImageContentService.java | 1 - .../app/rest/iiif/service/RelatedService.java | 1 - .../app/rest/iiif/service/SeeAlsoService.java | 1 - .../app/rest/iiif/IIIFRestRepositoryIT.java | 32 +++++++++++++++++++ 12 files changed, 32 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java index 31b2abc5c8..b9c29d8762 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.iiif.model.generator; import java.util.ArrayList; import java.util.List; - import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.Motivation; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java index a107315fe3..af4c7e5565 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.iiif.model.generator; import java.util.ArrayList; import java.util.List; - import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.openannotation.Annotation; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java index 4f32fff6cd..87a737bfef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.enums.ViewingHint; -import org.springframework.stereotype.Component; /** * API 2.1.1 ViewingHint is a hint to the client that suggests the appropriate method of diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index 0b10410dcd..6678f56882 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.iiif.model.generator; import java.util.ArrayList; import java.util.List; - import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.ImageContent; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java index 85f821de7b..04ae16e500 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.iiif.model.generator; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; - import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.Profile; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 4f373fb62b..e56ba3265f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.iiif.model.generator; import java.net.URI; import java.util.ArrayList; import java.util.List; - import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.ImageContent; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java index b740f36505..217913fc4b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.MetadataEntry; -import org.springframework.stereotype.Component; /** * Wraps the domain model metadata property. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java index 9a6ea16aa8..4fa99fbac8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.iiif.model.generator; import java.util.ArrayList; import java.util.List; - import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java index 240cd3767c..7b8a5ec011 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java @@ -13,7 +13,6 @@ import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator; import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; import org.dspace.services.ConfigurationService; -import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java index d215788790..3b6596c102 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.iiif.service; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; import org.dspace.content.Item; import org.dspace.services.ConfigurationService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java index be1d20bd93..f737fb1153 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java @@ -12,7 +12,6 @@ import java.util.UUID; import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; import org.dspace.services.ConfigurationService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java index d06eb8d266..a6909b423f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java @@ -576,4 +576,36 @@ public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { Matchers.containsString(bitstream2.getID() + "/content"))); } + + @Test + public void searchRequestShouldFailIT () 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") + .withEntityType("IIIF") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder. + createBitstream(context, publicItem1, is, IIIFBundle) + .withName("IMG1") + .withMimeType("image/jpeg") + .build(); + } + + context.restoreAuthSystemState(); + + // Expect a 501 (not implemented) error. The search service requires plugin configuration. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest/search?q=test")) + .andExpect(status().isNotImplemented()); + + } } From e42b9dff99f93dbce0dc3e75b5987c757a0e0f76 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 27 Sep 2021 11:07:02 -0700 Subject: [PATCH 0339/1254] Minor iiif.cfg updates. --- dspace/config/modules/iiif.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index fe3ce7c43a..c8e55c3312 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -19,7 +19,7 @@ iiif.metadata.item = dc.contributor.* iiif.metadata.item = dc.description.abstract # metadata to be added to the canvas from the bitstream, labels are set in -the Messages.properties i18n file. It is possible to include the technical +# the Messages.properties i18n file. It is possible to include the technical # information stored in the bitstream format registry and in the checksum using the placeholder # @format@ to include the short description of the bitstream format as entered by the user or # stored in the registry @@ -59,4 +59,4 @@ iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams # default value for the canvas size. Can be overridden at the item, bundle or bitstream level # via the iiif.image.width e iiif.image.height metadata # iiif.canvas.default-width = 1200 -# iiif.canvas.default-heigth = 1600 \ No newline at end of file +# iiif.canvas.default-height = 1600 From a4a27f07959c3fbd3d205a74b73d8543d0c3feb5 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 27 Sep 2021 14:11:12 -0700 Subject: [PATCH 0340/1254] More updates after merging PR#1. --- .../app/rest/iiif/cache/CacheConfig.java | 2 +- .../model/generator/AnnotationGenerator.java | 22 +- .../generator/AnnotationListGenerator.java | 15 +- .../model/generator/BehaviorGenerator.java | 2 +- .../iiif/model/generator/CanvasGenerator.java | 4 +- .../model/generator/CanvasItemsGenerator.java | 21 +- .../generator/ContentAsTextGenerator.java | 2 +- .../generator/ContentSearchGenerator.java | 4 +- .../generator/ExternalLinksGenerator.java | 2 +- .../iiif/model/generator/IIIFResource.java | 2 +- .../iiif/model/generator/IIIFService.java | 2 +- .../rest/iiif/model/generator/IIIFValue.java | 2 +- .../generator/ImageContentGenerator.java | 5 +- .../generator/ImageServiceGenerator.java | 4 +- .../model/generator/ManifestGenerator.java | 50 +- .../generator/MetadataEntryGenerator.java | 2 +- .../model/generator/ProfileGenerator.java | 5 +- .../generator/PropertyValueGenerator.java | 2 +- .../iiif/model/generator/RangeGenerator.java | 42 +- .../generator/SearchResultGenerator.java | 11 +- .../iiif/service/AnnotationListService.java | 3 +- .../iiif/service/CanvasLookupService.java | 2 +- .../app/rest/iiif/service/CanvasService.java | 2 +- .../rest/iiif/service/ManifestService.java | 16 +- .../iiif/service/SearchAnnotationService.java | 2 +- .../app/rest/iiif/service/SearchService.java | 2 +- .../iiif/service/WordHighlightSolrSearch.java | 14 +- .../app/rest/iiif/service/util/IIIFUtils.java | 7 +- .../app/rest/iiif/IIIFControllerIT.java | 1 + .../app/rest/iiif/IIIFRestRepositoryIT.java | 611 ------------------ 30 files changed, 121 insertions(+), 740 deletions(-) delete mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java index 1f91623b25..ffd0f81125 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java @@ -12,7 +12,7 @@ import org.springframework.context.annotation.Configuration; /** * Enables Spring cache support. The configuration file is defined in - * application properties. Cache size limits are defined there. + * application properties. *

    spring.cache.jcache.config=classpath:iiif/cache/ehcache.xml

    */ @Configuration diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java index b9c29d8762..fbe7ca8a20 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java @@ -99,28 +99,32 @@ public class AnnotationGenerator implements IIIFResource { */ public AnnotationGenerator setWithin(List within) { for (ManifestGenerator manifest : within) { - this.manifests.add(manifest.generate()); + this.manifests.add(manifest.generateResource()); } return this; } @Override - public Resource generate() { - if (identifier == null || motivation == null) { - throw new RuntimeException("Annotations require both an identifier and a motivation"); + public Resource generateResource() { + if (identifier == null) { + throw new RuntimeException("Annotations require an identifier."); + } + Annotation annotation; + if (motivation != null) { + annotation = new Annotation(identifier, motivation); + } else { + annotation = new Annotation(identifier); } - Annotation annotation = new Annotation(identifier, motivation); - annotation.setWithin(manifests); // These optional annotation fields vary with the context. if (canvasGenerator != null) { - annotation.setOn(canvasGenerator.generate()); + annotation.setOn(canvasGenerator.generateResource()); } if (externalLinksGenerator != null) { - annotation.setResource(externalLinksGenerator.generate()); + annotation.setResource(externalLinksGenerator.generateResource()); } if (contentAsTextGenerator != null) { - annotation.setResource(contentAsTextGenerator.generate()); + annotation.setResource(contentAsTextGenerator.generateResource()); } return annotation; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java index af4c7e5565..a01a8aabf6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java @@ -18,11 +18,12 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * This generator wraps the domain model for the {@code AnnotationList}. There should be a single instance of - * this object per annotation list request. The {@code @RequestScope} provides a single instance created - * and available during complete lifecycle of the HTTP request. - *

    - * The model represents an ordered list of annotations.

    + * This generator wraps the domain model for the {@code AnnotationList}. + * + *

    Please note that this is a request scoped bean. This means that for each http request a + * different instance will be initialized by Spring and used to serve this specific request.

    + * + *

    The model represents an ordered list of annotations.

    */ @RequestScope @Component @@ -45,11 +46,11 @@ public class AnnotationListGenerator implements org.dspace.app.rest.iiif.model.g * @param annotation an annotation generator */ public void addResource(AnnotationGenerator annotation) { - this.annotations.add((Annotation) annotation.generate()); + this.annotations.add((Annotation) annotation.generateResource()); } @Override - public Resource generate() { + public Resource generateResource() { if (identifier == null) { throw new RuntimeException("Missing the required identifier for the annotation list."); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java index 87a737bfef..df275e8e02 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java @@ -25,7 +25,7 @@ public class BehaviorGenerator implements IIIFValue { } @Override - public ViewingHint generate() { + public ViewingHint generateValue() { if (type == null) { throw new RuntimeException("Type must be provided for viewing hint."); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java index cef34add84..bb71416d68 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java @@ -98,7 +98,7 @@ public class CanvasGenerator implements IIIFResource { MetadataEntryGenerator metadataEntryGenerator = new MetadataEntryGenerator(); metadataEntryGenerator.setField(field); metadataEntryGenerator.setValue(value, rest); - metadata.add(metadataEntryGenerator.generate()); + metadata.add(metadataEntryGenerator.generateValue()); } /** @@ -106,7 +106,7 @@ public class CanvasGenerator implements IIIFResource { * @return canvas model */ @Override - public Resource generate() { + public Resource generateResource() { /** * The Canvas resource typically includes image content. */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java index 566290f3a3..e23a7ffc7f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java @@ -18,14 +18,13 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * This generator wraps the domain model for a Presentation API 2.1.1 {@code Sequence}. There must be a single - * instance of this object per request. The {@code @RequestScope} provides a single instance created and available - * during complete lifecycle of the HTTP request. - *

    - * The IIIF sequence conveys the ordering of the views of the object. - *

    - *

    - * Sequence is removed with Presentation API version 3.0. Canvases are added to the Manifest items property instead. + * This generator wraps the domain model for a Presentation API 2.1.1 {@code Sequence}. The IIIF sequence + * conveys the ordering of the views of the object. + * + *

    Please note that this is a request scoped bean. This means that for each http request a + * different instance will be initialized by Spring and used to serve this specific request.

    + * + *

    Sequence is removed with Presentation API version 3.0. Canvases are added to the Manifest items property instead. *

    */ @RequestScope @@ -51,7 +50,7 @@ public class CanvasItemsGenerator implements IIIFResource { * @param otherContent generator for the resource */ public void addRendering(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { - this.renderings.add((OtherContent) otherContent.generate()); + this.renderings.add((OtherContent) otherContent.generateResource()); } /** @@ -59,13 +58,13 @@ public class CanvasItemsGenerator implements IIIFResource { * @param canvas generator for canvas */ public String addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { - Canvas resource = (Canvas) canvas.generate(); + Canvas resource = (Canvas) canvas.generateResource(); this.canvas.add(resource); return resource.getIdentifier().toString(); } @Override - public Resource generate() { + public Resource generateResource() { Sequence items = new Sequence(identifier); for (OtherContent r : renderings) { items.addRendering(r); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java index 8170b733f8..520acd0c1f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java @@ -26,7 +26,7 @@ public class ContentAsTextGenerator implements IIIFResource { } @Override - public Resource generate() { + public Resource generateResource() { if (text == null) { throw new RuntimeException("Missing required text for the text annotation."); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java index 04ae16e500..6ee1c23732 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java @@ -56,7 +56,7 @@ public class ContentSearchGenerator implements IIIFService { } @Override - public Service generate() { + public Service generateService() { if (identifier == null) { throw new RuntimeException("You must provide an identifier for the search service."); } @@ -71,7 +71,7 @@ public class ContentSearchGenerator implements IIIFService { } ArrayList profiles = new ArrayList<>(); profile.setIdentifier("http://iiif.io/api/search/0/search"); - profiles.add(profile.generate()); + profiles.add(profile.generateValue()); contentSearchService.setProfiles(profiles); return contentSearchService; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java index cb68e25299..3c4ec7e72f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java @@ -61,7 +61,7 @@ public class ExternalLinksGenerator implements IIIFResource { } @Override - public Resource generate() { + public Resource generateResource() { if (identifier == null) { throw new RuntimeException("External links annotation requires an identifier"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java index 7bf4c89f7e..cdf9a5794a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java @@ -15,6 +15,6 @@ public interface IIIFResource { * Creates and returns a resource model. * @return resource model */ - Resource generate(); + Resource generateResource(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java index 4ad2e2cfc8..e06c9ebb6d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java @@ -15,5 +15,5 @@ public interface IIIFService { * Creates and returns a service model * @return a service model */ - Service generate(); + Service generateService(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java index 240ec0066a..f88e3ed0b1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java @@ -13,5 +13,5 @@ public interface IIIFValue { * creates and returns a value model. * @return a value model. */ - Object generate(); + Object generateValue(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java index f36bbb76ef..373af5b39e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java @@ -21,7 +21,6 @@ import de.digitalcollections.iiif.model.sharedcanvas.Resource; */ public class ImageContentGenerator implements IIIFResource { - private org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator imageService; private final ImageContent imageContent; public ImageContentGenerator(@NotNull String identifier) { @@ -42,12 +41,12 @@ public class ImageContentGenerator implements IIIFResource { * @param imageService */ public ImageContentGenerator addService(ImageServiceGenerator imageService) { - this.imageContent.addService(imageService.generate()); + this.imageContent.addService(imageService.generateService()); return this; } @Override - public Resource generate() { + public Resource generateResource() { return imageContent; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java index 10bc1870ee..011d417862 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java @@ -27,12 +27,12 @@ public class ImageServiceGenerator implements IIIFService { * @param profile a profile generator */ public ImageServiceGenerator setProfile(ProfileGenerator profile) { - imageService.addProfile(profile.generate()); + imageService.addProfile(profile.generateValue()); return this; } @Override - public Service generate() { + public Service generateService() { return imageService; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index ce4a27e9e5..27c8602277 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -26,27 +26,19 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** -<<<<<<< HEAD - * This generator wraps a domain model for the {@code Manifest}. There should be a single instance of - * this object per request. The {@code @RequestScope} provides a single instance created and available during - * complete lifecycle of the HTTP request. + * This generator wraps a domain model for the {@code Manifest}. + *

    + * Please note that this is a request scoped bean. This mean that for each http request a + * different instance will be initialized by Spring and used to serve this specific request.

    *

    * The Manifest is an overall description of the structure and properties of the digital representation * of an object. It carries information needed for the viewer to present the digitized content to the user, * such as a title and other descriptive information about the object or the intellectual work that * it conveys. Each manifest describes how to present a single object such as a book, a photograph, - * or a statue. - *

    -======= - * The Manifest is an overall description of the structure and properties of the digital representation - * of an object. It carries information needed for the viewer to present the digitized content to the user, - * such as a title and other descriptive information about the object or the intellectual work that - * it conveys. Each manifest describes how to present a single object such as a book, a photograph, - * or a statue. - * - * Please note that this is a request scoped bean. This mean that for each http request a + * or a statue.

    + * + * Please note that this is a request scoped bean. This means that for each http request a * different instance will be initialized by Spring and used to serve this specific request. ->>>>>>> 4Science-pr */ @RequestScope @Component @@ -64,7 +56,7 @@ public class ManifestGenerator implements IIIFResource { private ContentSearchService searchService; private final List license = new ArrayList<>(); private final List metadata = new ArrayList<>(); - private final List ranges = new ArrayList<>(); + private final List ranges = new ArrayList<>(); /** * Sets the mandatory manifest identifier. @@ -87,7 +79,7 @@ public class ManifestGenerator implements IIIFResource { } public void addLogo(ImageContentGenerator logo) { - this.logo = (ImageContent) logo.generate(); + this.logo = (ImageContent) logo.generateResource(); } /** @@ -96,7 +88,7 @@ public class ManifestGenerator implements IIIFResource { */ public void addViewingHint(String viewingHint) { BehaviorGenerator hint = new BehaviorGenerator().setType(viewingHint); - this.viewingHint = hint.generate(); + this.viewingHint = hint.generateValue(); } /** @@ -105,7 +97,7 @@ public class ManifestGenerator implements IIIFResource { * @param sequence canvas list model (sequence) */ public void addSequence(CanvasItemsGenerator sequence) { - this.sequence = (Sequence) sequence.generate(); + this.sequence = (Sequence) sequence.generateResource(); } /** @@ -113,7 +105,7 @@ public class ManifestGenerator implements IIIFResource { * @param seeAlso other content model */ public void addSeeAlso(ExternalLinksGenerator seeAlso) { - this.seeAlso = (OtherContent) seeAlso.generate(); + this.seeAlso = (OtherContent) seeAlso.generateResource(); } /** @@ -121,7 +113,7 @@ public class ManifestGenerator implements IIIFResource { * @param thumbnail an image content generator */ public void addThumbnail(ImageContentGenerator thumbnail) { - this.thumbnail = (ImageContent) thumbnail.generate(); + this.thumbnail = (ImageContent) thumbnail.generateResource(); } /** @@ -129,7 +121,7 @@ public class ManifestGenerator implements IIIFResource { * @param related other content generator */ public void addRelated(ExternalLinksGenerator related) { - this.related = (OtherContent) related.generate(); + this.related = (OtherContent) related.generateResource(); } /** @@ -137,7 +129,7 @@ public class ManifestGenerator implements IIIFResource { * @param searchService search service generator */ public void addService(ContentSearchGenerator searchService) { - this.searchService = (ContentSearchService) searchService.generate(); + this.searchService = (ContentSearchService) searchService.generateService(); } /** @@ -147,7 +139,7 @@ public class ManifestGenerator implements IIIFResource { */ public void addMetadata(String field, String value, String... rest) { MetadataEntryGenerator meg = new MetadataEntryGenerator().setField(field).setValue(value, rest); - metadata.add(meg.generate()); + metadata.add(meg.generateValue()); } /** @@ -163,7 +155,7 @@ public class ManifestGenerator implements IIIFResource { * @param value the description value */ public void addDescription(String value) { - description = new PropertyValueGenerator().getPropertyValue(value).generate(); + description = new PropertyValueGenerator().getPropertyValue(value).generateValue(); } /** @@ -171,11 +163,11 @@ public class ManifestGenerator implements IIIFResource { * @param rangeGenerator to add */ public void addRange(RangeGenerator rangeGenerator) { - ranges.add(rangeGenerator); + ranges.add((Range) rangeGenerator.generateResource()); } @Override - public Resource generate() { + public Resource generateResource() { if (identifier == null) { throw new RuntimeException("The Manifest resource requires an identifier."); @@ -195,8 +187,8 @@ public class ManifestGenerator implements IIIFResource { manifest.addSequence(sequence); } if (ranges != null && ranges.size() > 0) { - for (RangeGenerator range : ranges) { - manifest.addRange((Range) range.generate()); + for (Range range : ranges) { + manifest.addRange(range); } } if (metadata.size() > 0) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java index 79622703f3..1a3fbbf9c9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java @@ -40,7 +40,7 @@ public class MetadataEntryGenerator implements IIIFValue { } @Override - public MetadataEntry generate() { + public MetadataEntry generateValue() { PropertyValue metadataValues; if (rest != null && rest.length > 0) { metadataValues = new PropertyValue(value, rest); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java index 004f540edb..947d5cfbb0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java @@ -15,8 +15,7 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** - * This class wraps the domain model service profile. Spring can inject unique - * instances of this generator into services using the prototype scope. + * This class wraps the domain model service profile. */ @Scope("prototype") @Component @@ -32,7 +31,7 @@ public class ProfileGenerator implements IIIFValue { } @Override - public Profile generate() { + public Profile generateValue() { try { return new Profile(new URI(identifier)); } catch (URISyntaxException e) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java index d5ba42a9cc..2124580c65 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java @@ -28,7 +28,7 @@ public class PropertyValueGenerator implements IIIFValue { } @Override - public PropertyValue generate() { + public PropertyValue generateValue() { return propertyValue; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java index 27830dba31..54ff794d47 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java @@ -21,23 +21,22 @@ import org.dspace.app.rest.iiif.service.RangeService; * * In Presentation API version 2.1.1, adding a range to the manifest allows the client to display a structured * hierarchy to enable the user to navigate within the object without merely stepping through the current sequence. - * The rationale for separating ranges from sequences is that there is likely to be overlap between different ranges, - * such as the physical structure of a book compared to the textual structure of the work. -<<<<<<< HEAD -======= * * This is used to populate the "structures" element of the Manifest. The structure is derived from the iiif.toc * metadata and the ordered sequence of bitstreams (canvases) ->>>>>>> 4Science-pr */ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { private String identifier; private String label; private final List canvasList = new ArrayList<>(); - private final List rangesList = new ArrayList<>(); - private RangeService rangeService; + private final List rangesList = new ArrayList<>(); + private final RangeService rangeService; + /** + * The {@code RangeService} is used for defining hierarchical sub ranges. + * @param rangeService range service + */ public RangeGenerator(RangeService rangeService) { this.rangeService = rangeService; } @@ -59,7 +58,7 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. } /** - * Sets range label. + * Sets the optional range label. * @param label range label */ public RangeGenerator setLabel(String label) { @@ -72,12 +71,25 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. * @param canvas list of canvas generators */ public RangeGenerator addCanvas(CanvasGenerator canvas) { - canvasList.add((Canvas) canvas.generate()); + canvasList.add((Canvas) canvas.generateResource()); return this; } + /** + * Sets the range identifier and adds a sub range to the ranges list. + * @param range range generator + */ + public void addSubRange(RangeGenerator range) { + range.setIdentifier(identifier + "-" + rangesList.size()); + RangeGenerator rangeReference = rangeService.getRangeReference(range); + rangesList.add((Range) rangeReference.generateResource()); + } + @Override - public Resource generate() { + public Resource generateResource() { + if (identifier == null) { + throw new RuntimeException("The Range resource requires an identifier."); + } Range range; if (label != null) { range = new Range(identifier, label); @@ -87,15 +99,9 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator. for (Canvas canvas : canvasList) { range.addCanvas(canvas); } - for (RangeGenerator rg : rangesList) { - range.addRange((Range) rg.generate()); + for (Range rangeResource : rangesList) { + range.addRange(rangeResource); } return range; } - - public void addSubRange(RangeGenerator range) { - range.setIdentifier(identifier + "-" + rangesList.size()); - RangeGenerator rangeReference = rangeService.getRangeReference(range); - rangesList.add(rangeReference); - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java index 81facc5446..46ccb68fc2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java @@ -17,9 +17,10 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** - * This generator wraps a domain model for a {@code SearchResult}. There should be a single instance of - * this object per search request. The {@code @RequestScope} provides a single instance created and available during - * complete lifecycle of the HTTP request. + * This generator wraps a domain model for a {@code SearchResult}. + * + *

    Please note that this is a request scoped bean. This means that for each http request a + * different instance will be initialized by Spring and used to serve this specific request.

    */ @RequestScope @Component @@ -33,11 +34,11 @@ public class SearchResultGenerator implements IIIFResource { } public void addResource(AnnotationGenerator annotation) { - annotations.add((Annotation) annotation.generate()); + annotations.add((Annotation) annotation.generateResource()); } @Override - public Resource generate() { + public Resource generateResource() { SearchResult searchResult = new SearchResult(identifier); searchResult.setResources(annotations); return searchResult; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java index ff7096dc63..4094b74900 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java @@ -95,10 +95,11 @@ public class AnnotationListService extends AbstractResourceService { throw new RuntimeException(e.getMessage(), e); } AnnotationGenerator annotation = new AnnotationGenerator(IIIF_ENDPOINT + bitstream.getID()) + .setMotivation(AnnotationGenerator.LINKING) .setResource(getLinksGenerator(mimetype, bitstream)); annotationList.addResource(annotation); } - return utils.asJson(annotationList.generate()); + return utils.asJson(annotationList.generateResource()); } private ExternalLinksGenerator getLinksGenerator(String mimetype, Bitstream bitstream) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java index 9c2b108cee..859f6cd7c1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java @@ -53,7 +53,7 @@ public class CanvasLookupService extends AbstractResourceService { } catch (SQLException e) { throw new RuntimeException(e); } - return utils.asJson(canvasGenerator.generate()); + return utils.asJson(canvasGenerator.generateResource()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index 39e0a36137..763cd6b47a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -93,7 +93,7 @@ public class CanvasService extends AbstractResourceService { return addMetadata(context, bitstream, new CanvasGenerator(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) - .addImage(image.generate()).addThumbnail(thumb.generate()).setHeight(canvasHeight) + .addImage(image.generateResource()).addThumbnail(thumb.generateResource()).setHeight(canvasHeight) .setWidth(canvasWidth).setLabel(label)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 23be419a90..950fed25e3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -35,21 +35,13 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; /** -<<<<<<< HEAD * This service creates the manifest. There should be a single instance of this service per request. * The {@code @RequestScope} provides a single instance created and available during complete lifecycle - * of the HTTP request. -======= - * Generates IIIF Manifest JSON response for a DSpace Item. Please note that - * this is a request scoped bean. This mean that for each http request a - * different instance will be initialized by Spring and used to serve this - * specific request. This is needed because some configuration are cached in the + * of the HTTP request. This is needed because some configurations are cached in the * instance. Moreover, many injected dependencies are also request scoped or * prototype (that will turn in a request scope when injected in a request scope - * bean). The generators need to be request scoped as they act as builder - * storing the object state during each incremental building step until the - * final object is returned (IIIF Resource) ->>>>>>> 4Science-pr + * bean). The generators for top-level domain objects need to be request scoped as they act as a builder + * storing the object state during each incremental building step until the final object is returned (IIIF Resource). */ @RequestScope @Component @@ -109,7 +101,7 @@ public class ManifestService extends AbstractResourceService { */ public String getManifest(Item item, Context context) { populateManifest(item, context); - return utils.asJson(manifestGenerator.generate()); + return utils.asJson(manifestGenerator.generateResource()); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java index 7bf1fab919..f97e794f23 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java @@ -15,6 +15,6 @@ public interface SearchAnnotationService { String getSolrSearchResponse(UUID uuid, String query); - boolean getSearchPlugin(String className); + boolean useSearchPlugin(String className); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java index edaad00ea3..d7931c4979 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java @@ -50,7 +50,7 @@ public class SearchService extends AbstractResourceService { public String searchWithinManifest(UUID uuid, String query) throws NotImplementedException { if (searchPlugin != null) { for (SearchAnnotationService service : annotationService) { - if (service.getSearchPlugin(searchPlugin)) { + if (service.useSearchPlugin(searchPlugin)) { service.initializeQuerySettings(IIIF_ENDPOINT, getManifestId(uuid)); return service.getSolrSearchResponse(uuid, query); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java index ff6a08df65..fc24313b92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java @@ -39,18 +39,16 @@ import org.dspace.discovery.SolrSearchCore; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; /** - * This service provides methods for executing a solr search against the solr index. There should be a single - * instance of this service per request. The {@code @RequestScope} provides a single instance created and - * available during complete lifecycle of the HTTP request. + * This service implements methods for executing a solr search and creating IIIF search result annotations. *

    * https://github.com/dbmdz/solr-ocrhighlighting */ -@RequestScope +@Scope("prototype") @Component public class WordHighlightSolrSearch implements SearchAnnotationService { @@ -76,7 +74,7 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { @Override - public boolean getSearchPlugin(String className) { + public boolean useSearchPlugin(String className) { return className.contentEquals(WordHighlightSolrSearch.class.getCanonicalName()); } @@ -185,7 +183,7 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { JsonObject body = gson.fromJson(json, JsonObject.class); if (body == null) { log.warn("Unable to process json response."); - return utils.asJson(searchResult.generate()); + return utils.asJson(searchResult.generateResource()); } // outer ocr highlight element JsonObject highs = body.getAsJsonObject("ocrHighlighting"); @@ -206,7 +204,7 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { } } - return utils.asJson(searchResult.generate()); + return utils.asJson(searchResult.generateResource()); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 17a5438d0d..1ac4e1c934 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -257,14 +257,13 @@ public class IIIFUtils { /** * Return all the bitstreams in the item to be used as annotations - * - * @param context the DSpace Context + * * @param item the DSpace item * @return a not null list of bitstreams to use as IIIF resources in the * manifest */ public List getSeeAlsoBitstreams(Item item) { - List seeAlsoBitstreams = new ArrayList(); + List seeAlsoBitstreams = new ArrayList<>(); List bundles = item.getBundles(OTHER_CONTENT_BUNDLE); if (bundles.size() > 0) { for (Bundle bundle : bundles) { @@ -293,7 +292,7 @@ public class IIIFUtils { * Return the custom iiif description for the resource or the provided default if none * * @param dso the dspace object to use as iiif resource - * @param defaultLabel the default description to return if none is specified in the + * @param defaultDescription the default description to return if none is specified in the * metadata * @return the iiif label for the dspace object */ 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 a35b1cc524..0929d481ba 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 @@ -933,6 +933,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.@type", is("sc:AnnotationList"))) .andExpect(jsonPath("$.resources[0].@type", is("oa:Annotation"))) + .andExpect(jsonPath("$.resources[0].motivation", is ("oa:linking"))) .andExpect(jsonPath("$.resources[0].resource.@id", Matchers.containsString(bitstream2.getID() + "/content"))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java deleted file mode 100644 index a6909b423f..0000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFRestRepositoryIT.java +++ /dev/null @@ -1,611 +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.iiif; - -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.InputStream; - -import org.apache.commons.codec.CharEncoding; -import org.apache.commons.io.IOUtils; -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.builder.BitstreamBuilder; -import org.dspace.builder.CollectionBuilder; -import org.dspace.builder.CommunityBuilder; -import org.dspace.builder.ItemBuilder; -import org.dspace.content.Bitstream; -import org.dspace.content.Collection; -import org.dspace.content.Item; -import org.hamcrest.Matchers; -import org.junit.Test; - -public class IIIFRestRepositoryIT extends AbstractControllerIntegrationTest { - - public static final String IIIFBundle = "IIIF"; - - /** - * info.json set for individual canvases. - */ - String info = "{\"globalDefaults\":{\"activated\": false,\"label\": \"\",\"width\": 0,\"height\": 0}," + - "\"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, \"height\": 4220, \"pos\": 0}]" + - ",\"structures\": []}"; - - /** - * info.json with structures and global canvas setting. - */ - String infoWithStructures = "{\"globalDefaults\":" + - "{\"activated\": true,\"label\": \"Global\",\"width\": 2000,\"height\": 3000}," + - "\"canvases\":[]," + - "\"structures\": " + - "[{\"label\":\"Section 1\",\"start\":1}," + - "{\"label\":\"Section 2\",\"start\":2}]" + - "}"; - - /** - * info.json defaulting to global canvas settings. - */ - String globalInfoConfig = "{\"globalDefaults\":{\"activated\": true,\"label\": \"Global\",\"width\": 2000," + - "\"height\": 3000}, \"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, " + - "\"height\": 4220, \"pos\": 0}],\"structures\": []}"; - - @Test - public void missingBundleTest() 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") - .withEntityType("IIIFSearchable") - .build(); - context.restoreAuthSystemState(); - // Status 500 - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().is(500)); - - } - - @Test - public void findOneIIIFSearchableEntityTypeIT() 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") - .withEntityType("IIIFSearchable") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - Bitstream bitstream1 = null; - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream2") - .withMimeType("image/jpeg") - .build(); - } - - context.restoreAuthSystemState(); - - // Default canvas size and label. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .andExpect(jsonPath("$.service.profile", is("http://iiif.io/api/search/0/search"))) - .andExpect(jsonPath("$.thumbnail.@id", Matchers.containsString("/iiif/2/" - + bitstream1.getID()))) - .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(1200))) - .andExpect(jsonPath("$.related.@id", - Matchers.containsString("/items/" + publicItem1.getID()))); - } - - @Test - public void findOneIIIFSearchableWithGlobalConfigIT() 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") - .withEntityType("IIIFSearchable") - .build(); - - String bitstreamContent = "ThisIsSomeText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - - try (InputStream is = IOUtils.toInputStream(this.globalInfoConfig, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, "IIIF") - .withName("info.json") - .withMimeType("application/json") - .build(); - } - - context.restoreAuthSystemState(); - // Expect canvas label, width and height to match bitstream description. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) - .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) - .andExpect(jsonPath("$.service").exists()); - } - - @Test - public void findOneIIIFSearchableWithInfoJsonIT() 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") - .withEntityType("IIIFSearchable") - .build(); - - String bitstreamContent = "ThisIsSomeText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - - try (InputStream is = IOUtils.toInputStream(this.info, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, "IIIF") - .withName("info.json") - .withMimeType("application/json") - .build(); - } - - context.restoreAuthSystemState(); - // Expect canvas label, width and height to match bitstream description. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(3163))) - .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(4220))) - .andExpect(jsonPath("$.service").exists()); - } - - @Test - public void findOneIIIFEntityPagedHintIT() 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") - .withMetadata("dc", "rights", "uri", "https://license.org") - .withEntityType("IIIF") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - - String bitstreamContent2 = "ThisIsSomeDummyText2"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream2") - .withMimeType("image/jpeg") - .build(); - } - - String bitstreamContent3 = "ThisIsSomeDummyText3"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream3") - .withMimeType("image/jpeg") - .build(); - } - - context.restoreAuthSystemState(); - - // With more than 2 bitstreams in IIIF bundle, the sequence viewing hint should be "paged" - // unless that has been changed in dspace configuration. This test assumes that DSpace - // has been configured to return the "individuals" hint for documents to better support - // search results in Mirador. That is the current dspace.cfg default setting. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.license", is("https://license.org"))) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .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("$.viewingHint", is("individuals"))) - .andExpect(jsonPath("$.service").doesNotExist()); - - } - - @Test - public void findOneWithStructures() 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") - .withEntityType("IIIFSearchable") - .build(); - - String bitstreamContent = "ThisIsSomeText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream2") - .withMimeType("image/jpeg") - .build(); - } - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder - .createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream3") - .withMimeType("image/jpeg") - .build(); - } - - try (InputStream is = IOUtils.toInputStream(this.infoWithStructures, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, "IIIF") - .withName("info.json") - .withMimeType("application/json") - .build(); - } - - context.restoreAuthSystemState(); - // expect structures elements with label and canvas id. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) - .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) - .andExpect(jsonPath("$.structures[1].label", is("Section 2"))) - .andExpect(jsonPath("$.structures[1].canvases[0]", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c2"))) - .andExpect(jsonPath("$.service").exists()); - } - - @Test - public void findOneIIIFEntityTypeIT() 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") - .withMetadata("dc", "rights", "uri", "https://license.org") - .withEntityType("IIIF") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - - context.restoreAuthSystemState(); - - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.license", is("https://license.org"))) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .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("$.service").doesNotExist()); - - } - - @Test - public void findOneIIIFWithOtherContentIT() 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") - .withMetadata("dc", "rights", "uri", "https://license.org") - .withEntityType("IIIF") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, "OtherContent") - .withName("file.xml") - .withMimeType("application/xml") - .build(); - } - - context.restoreAuthSystemState(); - - // Expect seeAlso annotation list. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.license", is("https://license.org"))) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .andExpect(jsonPath("$.seeAlso.@type", is("sc:AnnotationList"))) - .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("$.service").doesNotExist()); - - } - - @Test - public void findOneUsingOriginalBundleIgnoreFileIT() 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") - .withEntityType("IIIF") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withMimeType("image/jpeg") - .build(); - } - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withMimeType("application/pdf") - .build(); - } - - context.restoreAuthSystemState(); - - // Image in the ORIGINAL bundle added as canvas; PDF ignored... - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .andExpect(jsonPath("$.sequences[0].canvases", Matchers.hasSize(1))) - .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("$.service").doesNotExist()); - } - - - @Test - public void findOneCanvas() 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") - .withEntityType("IIIF") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("IMG1") - .withMimeType("image/jpeg") - .build(); - } - context.restoreAuthSystemState(); - - // Single canvas. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/canvas/c0")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.@type", is("sc:Canvas"))) - .andExpect(jsonPath("$.images[0].@type", is("oa:Annotation"))); - } - - @Test - public void missingCanvas() 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") - .withEntityType("IIIF") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("IMG1") - .withMimeType("image/jpeg") - .build(); - } - context.restoreAuthSystemState(); - // Status 500. The item contains only one bitstream. The item manifest likewise contains one canvas. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/canvas/c2")) - .andExpect(status().is(500)); - } - - @Test - public void getAnnotationListSeeAlso() 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") - .withEntityType("IIIF") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("IMG1") - .withMimeType("image/jpeg") - .build(); - } - Bitstream bitstream2 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream2 = BitstreamBuilder. - createBitstream(context, publicItem1, is, "OtherContent") - .withName("file.xml") - .withMimeType("application/xml") - .build(); - } - - context.restoreAuthSystemState(); - - // Expect seeAlso AnnotationList if the dspace item includes an OtherContent bundle. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest/seeAlso")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.@type", is("sc:AnnotationList"))) - .andExpect(jsonPath("$.resources[0].@type", is("oa:Annotation"))) - .andExpect(jsonPath("$.resources[0].resource.@id", - Matchers.containsString(bitstream2.getID() + "/content"))); - - } - - @Test - public void searchRequestShouldFailIT () 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") - .withEntityType("IIIF") - .build(); - - String bitstreamContent = "ThisIsSomeDummyText"; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - BitstreamBuilder. - createBitstream(context, publicItem1, is, IIIFBundle) - .withName("IMG1") - .withMimeType("image/jpeg") - .build(); - } - - context.restoreAuthSystemState(); - - // Expect a 501 (not implemented) error. The search service requires plugin configuration. - getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest/search?q=test")) - .andExpect(status().isNotImplemented()); - - } -} From 327f800c5f9e48f7fbe7406d60a14af957be813e Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 27 Sep 2021 16:22:59 -0700 Subject: [PATCH 0341/1254] Fixed lgtm alerts. --- .../dspace/app/rest/iiif/model/generator/ManifestGenerator.java | 2 +- .../java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java index 27c8602277..1e66c579fe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java @@ -186,7 +186,7 @@ public class ManifestGenerator implements IIIFResource { if (sequence != null) { manifest.addSequence(sequence); } - if (ranges != null && ranges.size() > 0) { + if (ranges.size() > 0) { for (Range range : ranges) { manifest.addRange(range); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 1ac4e1c934..40d0a16e9d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -142,7 +142,7 @@ public class IIIFUtils { * Return all the bitstreams in the bundle to be used as IIIF resources * * @param context the DSpace Context - * @param item the DSpace Bundle + * @param bundle the DSpace Bundle * @return a not null list of bitstreams to use as IIIF resources in the * manifest */ From 267f24ecf0ff0a14ee9d5dec176cb4ad0fa1006f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 28 Sep 2021 10:43:10 +0100 Subject: [PATCH 0342/1254] fix indentation - 2nd try --- .../org/dspace/external/openaire-project.xml | 45 +- .../org/dspace/external/openaire-projects.xml | 410 ++++++++---------- .../provider/impl/openaire-project.xml | 45 +- .../provider/impl/openaire-projects.xml | 410 ++++++++---------- 4 files changed, 392 insertions(+), 518 deletions(-) diff --git a/dspace-api/src/test/resources/org/dspace/external/openaire-project.xml b/dspace-api/src/test/resources/org/dspace/external/openaire-project.xml index 1cfa498bf1..90fc11e39a 100644 --- a/dspace-api/src/test/resources/org/dspace/external/openaire-project.xml +++ b/dspace-api/src/test/resources/org/dspace/external/openaire-project.xml @@ -1,44 +1,39 @@

    - (oaftype exact project) and (projectcode_nt exact "110062") and - (fundershortname exact "FCT") + (oaftype exact project) and (projectcode_nt exact "110062") and (fundershortname exact "FCT") en_US 10 1 1 - - +
    -
    +
    fct_________::59523e9f4736c2cab70a470f088b53dd 2021-08-06 2021-08-06
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + fct_________::110062 - http://www.fct.pt/apoios/projectos/consulta/vglobal_projecto.phtml.en?idProjecto=110062&idElemConcurso=3734 - + http://www.fct.pt/apoios/projectos/consulta/vglobal_projecto.phtml.en?idProjecto=110062&idElemConcurso=3734 110062 PTDC/AGR-ALI/110062/2009 - Portuguese Wild Mushrooms: Chemical characterization and - functional study of antiproliferative and proapoptotic properties - in cancer cell lines + Portuguese Wild Mushrooms: Chemical characterization and functional study of antiproliferative and proapoptotic properties in cancer cell lines 2010-12-24 2013-12-23 PTDC/2009 - Agricultural and Forestry Sciences - Food Science and - Technology + Agricultural and Forestry Sciences - Food Science and Technology 0 false false @@ -56,7 +51,7 @@ fct_________::FCT::5876-PPCDTI 5876-PPCDTI 5876-PPCDTI - + fct:program @@ -65,18 +60,16 @@ false 0.900 null - + - - + + - - + \ No newline at end of file diff --git a/dspace-api/src/test/resources/org/dspace/external/openaire-projects.xml b/dspace-api/src/test/resources/org/dspace/external/openaire-projects.xml index 1d759e3653..07b4c687b4 100644 --- a/dspace-api/src/test/resources/org/dspace/external/openaire-projects.xml +++ b/dspace-api/src/test/resources/org/dspace/external/openaire-projects.xml @@ -6,28 +6,27 @@ 10 1 77 - - +
    -
    +
    rcuk________::98b50e6e0715fad40627833c7030d3c3 2018-02-06 2021-02-20
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + rcuk________::103679 103679 - Mushroom Robo-Pic - Development of an autonomous robotic - mushroom picking system + Mushroom Robo-Pic - Development of an autonomous robotic mushroom picking system 2017-10-01 2019-06-30 0 @@ -47,7 +46,7 @@ rcuk________::RCUK::Innovate UK Innovate UK Innovate UK - + rcuk:fundingStream @@ -56,41 +55,40 @@ false 0.900 null - + - rcuk________::ebafcf5f45afa2e9807f981e668db66b + provenanceaction="sysimport:crosswalk:entityregistry"> + rcuk________::ebafcf5f45afa2e9807f981e668db66b Littleport Mushroom Farms Llp - + - + -
    +
    rcuk________::6ac77c83ee0d98c433f91f3dc83074b2 2017-11-04 2021-02-20
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + rcuk________::752540 752540 Exending Shelf Life of Mushroom Growing Kits @@ -113,7 +111,7 @@ rcuk________::RCUK::Innovate UK Innovate UK Innovate UK - + rcuk:fundingStream @@ -122,49 +120,46 @@ false 0.900 null - + - rcuk________::f2a533e22408279c50f647779633cf69 + provenanceaction="sysimport:crosswalk:entityregistry"> + rcuk________::f2a533e22408279c50f647779633cf69 Espresso Mushroom Company Ltd - + - + -
    +
    arc_________::e1da9b244237847b24379fb1b11fb151 2015-08-24 2018-11-20
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> arc_________::LP0220040 - http://purl.org/au-research/grants/arc/LP0220040 - + http://purl.org/au-research/grants/arc/LP0220040 LP0220040 - Use of Organic Residues in Edible Mushroom Production - + Use of Organic Residues in Edible Mushroom Production 2002-01-01 2003-12-31 - compost,exotic mushrooms,mushroom production,organic - wastes,peat,wood processing wastes + compost,exotic mushrooms,mushroom production,organic wastes,peat,wood processing wastes 0 false false @@ -182,7 +177,7 @@ arc_________::ARC::Linkage Projects Linkage Projects Linkage Projects - + arc:fundingStream @@ -191,36 +186,34 @@ false 0.900 null - + - - + + -
    +
    rcuk________::c137d9bfad46b1ebcc9b3f06e6eb5683 2018-08-01 2021-02-20
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + rcuk________::133611 133611 - The development of a mushroom harvesting machine to - increase yield and production while reducing waste and labour - shortage risk + The development of a mushroom harvesting machine to increase yield and production while reducing waste and labour shortage risk 2018-07-01 2019-06-30 0 @@ -240,7 +233,7 @@ rcuk________::RCUK::Innovate UK Innovate UK Innovate UK - + rcuk:fundingStream @@ -249,93 +242,59 @@ false 0.900 null - + - rcuk________::560dfc3b58d1ab0d8a8957a943a76962 - + provenanceaction="sysimport:crosswalk:entityregistry"> + rcuk________::560dfc3b58d1ab0d8a8957a943a76962 + Mushroom Machine Company Limited - + -
    +
    corda__h2020::c97f7d6f1ff338991c0ec20b33ddb1e0 2018-07-21 2021-07-19
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + corda__h2020::820352 820352 Smartmushroom - Smart MAnagement of spent mushRoom subsTrate to lead the - MUSHROOM sector towards a circular economy + Smart MAnagement of spent mushRoom subsTrate to lead the MUSHROOM sector towards a circular economy 2018-08-01 2021-01-31 H2020-EIC-FTI-2018-2020 0 false - Fast Track to Innovation (FTI) + Fast Track to Innovation (FTI) true false - Waste from animal breeding and agriculture, specifically - horse and chicken manure and wheat straw, are the raw materials - of the growing substrate of mushroom. To grow 1 tonne of - mushroom, 3 to 4 tonnes of substrate are needed. However, when - mushroom production is completed the substrate cannot be used for - another growing cycle due to the depletion of nutrients needed - for mushroom growing and it is called Spent Mushroom Substrate - (SMS) and becomes a waste that should be managed according to - regulations. In Europe, c.a. 3.65 million tons of SMS are - generated each year. SMS is a high-moisture content bulk material - rich in organic matter and nutrients and it could be reused in - agriculture by adding it to the soils as amendment or mulch or - weathered to be reused as casing soil. However, nitrates - directive set a disposal limit that makes that large quantities - of SMS cannot be simply spread in soils next to growers’ - facilities, as there is a high risk of leachates and water - pollution. Due to its low bulk density and high water content, - transportation costs are high and therefore storage is becoming a - sound problem. SmartMUSHROOM aims to increase mushroom growers’ - waste management efficiency by using a new technology which allow - them to obtain enough biogas from fresh SMS to dry a mixture of - digestate and additional fresh SMS and pelletize it targeting to - obtain a marketable high-quality organic fertilizer rich in - organic matter and in nutrients, easy to handle, store and - transport to any farming region in Europe. A perfect example of - biobased circular economy. The aim of the project is to build a - pilot plant to demonstrate the technology and find the best - commercial formulation for the pellets to enter organic farming - market. After the end of project we aim to build at least 18 - treatment plants that will place in market 153,000 tonnes of - SMS-pellets, generating a total Turnover of 54M€ in the period - 2021-2025 and up to 105 related new jobs. + Waste from animal breeding and agriculture, specifically horse and chicken manure and wheat straw, are the raw materials of the growing substrate of mushroom. To grow 1 tonne of mushroom, 3 to 4 tonnes of substrate are needed. However, when mushroom production is completed the substrate cannot be used for another growing cycle due to the depletion of nutrients needed for mushroom growing and it is called Spent Mushroom Substrate (SMS) and becomes a waste that should be managed according to regulations. In Europe, c.a. 3.65 million tons of SMS are generated each year. SMS is a high-moisture content bulk material rich in organic matter and nutrients and it could be reused in agriculture by adding it to the soils as amendment or mulch or weathered to be reused as casing soil. However, nitrates directive set a disposal limit that makes that large quantities of SMS cannot be simply spread in soils next to growers’ facilities, as there is a high risk of leachates and water pollution. Due to its low bulk density and high water content, transportation costs are high and therefore storage is becoming a sound problem. SmartMUSHROOM aims to increase mushroom growers’ waste management efficiency by using a new technology which allow them to obtain enough biogas from fresh SMS to dry a mixture of digestate and additional fresh SMS and pelletize it targeting to obtain a marketable high-quality organic fertilizer rich in organic matter and in nutrients, easy to handle, store and transport to any farming region in Europe. A perfect example of biobased circular economy. The aim of the project is to build a pilot plant to demonstrate the technology and find the best commercial formulation for the pellets to enter organic farming market. After the end of project we aim to build at least 18 treatment plants that will place in market 153,000 tonnes of SMS-pellets, generating a total Turnover of 54M€ in the period 2021-2025 and up to 105 related new jobs. EUR 2977940.0 2264140.0 @@ -356,7 +315,7 @@ ec__________::EC::H2020 H2020 Horizon 2020 Framework Programme - + ec:h2020fundings @@ -367,75 +326,69 @@ false 0.900 null - + - pending_org_::39d5641e14f7cfe56e3e836199e33eba + provenanceaction="sysimport:crosswalk:entityregistry"> + pending_org_::39d5641e14f7cfe56e3e836199e33eba ECOBELIEVE DOO - + ECOBELIEVE DOO GRKINJA - pending_org_::3c128a988f1404fed53e1cd8a61df51b - Asociacion Profesional de Productores de Compost y - Hongos, de la Rioja, Navarra y Aragón - + provenanceaction="sysimport:crosswalk:entityregistry"> + pending_org_::3c128a988f1404fed53e1cd8a61df51b + Asociacion Profesional de Productores de Compost y Hongos, de la Rioja, Navarra y Aragón + ASOCHAMP - pending_org_::7555b3c59a033e789ce6190a6c9f39aa - INVESTIGACION Y DESARROLLO CASTILLA Y LEON S.A - - + provenanceaction="sysimport:crosswalk:entityregistry"> + pending_org_::7555b3c59a033e789ce6190a6c9f39aa + INVESTIGACION Y DESARROLLO CASTILLA Y LEON S.A + IDECAL S.A. - pending_org_::4193497542c7b30e3f50f52414905579 + provenanceaction="sysimport:crosswalk:entityregistry"> + pending_org_::4193497542c7b30e3f50f52414905579 NOVIS GMBH - + NOVIS GMBH - + -
    +
    nwo_________::c091653b5930a5a11c748720167b04d7 2016-06-23 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nwo_________::2300148817 2300148817 Production of therapeutic proteins in mushroom @@ -451,8 +404,7 @@ nwo_________::NWO NWO - Netherlands Organisation for Scientific Research (NWO) - + Netherlands Organisation for Scientific Research (NWO) NL @@ -461,32 +413,32 @@ false 0.900 null - + - - + + -
    +
    nwo_________::5ee5a31d8c77215faef3bd35bd9696ff 2016-06-23 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nwo_________::2300148209 2300148209 Control of Verticillium fungicola on mushroom @@ -502,8 +454,7 @@ nwo_________::NWO NWO - Netherlands Organisation for Scientific Research (NWO) - + Netherlands Organisation for Scientific Research (NWO) NL @@ -512,32 +463,32 @@ false 0.900 null - + - - + + -
    +
    nwo_________::98df9c76cbd6537553284af850398659 2016-06-23 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nwo_________::2300147728 2300147728 Master switches of initiation of mushroom formation @@ -553,8 +504,7 @@ nwo_________::NWO NWO - Netherlands Organisation for Scientific Research (NWO) - + Netherlands Organisation for Scientific Research (NWO) NL @@ -563,36 +513,35 @@ false 0.900 null - + - - + + -
    +
    nwo_________::7ddc7f2f259735f312a27c54f6b2ee5d 2016-06-23 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nwo_________::2300164658 2300164658 - Push the white button; controlling mushroom formation - + Push the white button; controlling mushroom formation 2011-09-01 0 false @@ -604,8 +553,7 @@ nwo_________::NWO NWO - Netherlands Organisation for Scientific Research (NWO) - + Netherlands Organisation for Scientific Research (NWO) NL @@ -614,32 +562,32 @@ false 0.900 null - + - - + + -
    +
    nsf_________::c5fa29db776dc4f21919e12cbaea37eb 2016-03-11 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nsf_________::6112554 6112554 Respiratory Mechanisms in Cultivated Mushroom @@ -664,30 +612,26 @@ false 0.900 null - + - openorgs____::2f4b2e4dcb319a5f66e887d2fd555734 - + provenanceaction="sysimport:crosswalk:entityregistry"> + openorgs____::2f4b2e4dcb319a5f66e887d2fd555734 + University of Delaware UD - + - - - + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-project.xml b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-project.xml index 1cfa498bf1..90fc11e39a 100644 --- a/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-project.xml +++ b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-project.xml @@ -1,44 +1,39 @@
    - (oaftype exact project) and (projectcode_nt exact "110062") and - (fundershortname exact "FCT") + (oaftype exact project) and (projectcode_nt exact "110062") and (fundershortname exact "FCT") en_US 10 1 1 - - +
    -
    +
    fct_________::59523e9f4736c2cab70a470f088b53dd 2021-08-06 2021-08-06
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + fct_________::110062 - http://www.fct.pt/apoios/projectos/consulta/vglobal_projecto.phtml.en?idProjecto=110062&idElemConcurso=3734 - + http://www.fct.pt/apoios/projectos/consulta/vglobal_projecto.phtml.en?idProjecto=110062&idElemConcurso=3734 110062 PTDC/AGR-ALI/110062/2009 - Portuguese Wild Mushrooms: Chemical characterization and - functional study of antiproliferative and proapoptotic properties - in cancer cell lines + Portuguese Wild Mushrooms: Chemical characterization and functional study of antiproliferative and proapoptotic properties in cancer cell lines 2010-12-24 2013-12-23 PTDC/2009 - Agricultural and Forestry Sciences - Food Science and - Technology + Agricultural and Forestry Sciences - Food Science and Technology 0 false false @@ -56,7 +51,7 @@ fct_________::FCT::5876-PPCDTI 5876-PPCDTI 5876-PPCDTI - + fct:program @@ -65,18 +60,16 @@ false 0.900 null - + - - + + - - + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-projects.xml b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-projects.xml index 1d759e3653..07b4c687b4 100644 --- a/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-projects.xml +++ b/dspace-server-webapp/src/test/resources/org/dspace/external/provider/impl/openaire-projects.xml @@ -6,28 +6,27 @@ 10 1 77 - - +
    -
    +
    rcuk________::98b50e6e0715fad40627833c7030d3c3 2018-02-06 2021-02-20
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + rcuk________::103679 103679 - Mushroom Robo-Pic - Development of an autonomous robotic - mushroom picking system + Mushroom Robo-Pic - Development of an autonomous robotic mushroom picking system 2017-10-01 2019-06-30 0 @@ -47,7 +46,7 @@ rcuk________::RCUK::Innovate UK Innovate UK Innovate UK - + rcuk:fundingStream @@ -56,41 +55,40 @@ false 0.900 null - + - rcuk________::ebafcf5f45afa2e9807f981e668db66b + provenanceaction="sysimport:crosswalk:entityregistry"> + rcuk________::ebafcf5f45afa2e9807f981e668db66b Littleport Mushroom Farms Llp - + - + -
    +
    rcuk________::6ac77c83ee0d98c433f91f3dc83074b2 2017-11-04 2021-02-20
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + rcuk________::752540 752540 Exending Shelf Life of Mushroom Growing Kits @@ -113,7 +111,7 @@ rcuk________::RCUK::Innovate UK Innovate UK Innovate UK - + rcuk:fundingStream @@ -122,49 +120,46 @@ false 0.900 null - + - rcuk________::f2a533e22408279c50f647779633cf69 + provenanceaction="sysimport:crosswalk:entityregistry"> + rcuk________::f2a533e22408279c50f647779633cf69 Espresso Mushroom Company Ltd - + - + -
    +
    arc_________::e1da9b244237847b24379fb1b11fb151 2015-08-24 2018-11-20
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> arc_________::LP0220040 - http://purl.org/au-research/grants/arc/LP0220040 - + http://purl.org/au-research/grants/arc/LP0220040 LP0220040 - Use of Organic Residues in Edible Mushroom Production - + Use of Organic Residues in Edible Mushroom Production 2002-01-01 2003-12-31 - compost,exotic mushrooms,mushroom production,organic - wastes,peat,wood processing wastes + compost,exotic mushrooms,mushroom production,organic wastes,peat,wood processing wastes 0 false false @@ -182,7 +177,7 @@ arc_________::ARC::Linkage Projects Linkage Projects Linkage Projects - + arc:fundingStream @@ -191,36 +186,34 @@ false 0.900 null - + - - + + -
    +
    rcuk________::c137d9bfad46b1ebcc9b3f06e6eb5683 2018-08-01 2021-02-20
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + rcuk________::133611 133611 - The development of a mushroom harvesting machine to - increase yield and production while reducing waste and labour - shortage risk + The development of a mushroom harvesting machine to increase yield and production while reducing waste and labour shortage risk 2018-07-01 2019-06-30 0 @@ -240,7 +233,7 @@ rcuk________::RCUK::Innovate UK Innovate UK Innovate UK - + rcuk:fundingStream @@ -249,93 +242,59 @@ false 0.900 null - + - rcuk________::560dfc3b58d1ab0d8a8957a943a76962 - + provenanceaction="sysimport:crosswalk:entityregistry"> + rcuk________::560dfc3b58d1ab0d8a8957a943a76962 + Mushroom Machine Company Limited - + -
    +
    corda__h2020::c97f7d6f1ff338991c0ec20b33ddb1e0 2018-07-21 2021-07-19
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + corda__h2020::820352 820352 Smartmushroom - Smart MAnagement of spent mushRoom subsTrate to lead the - MUSHROOM sector towards a circular economy + Smart MAnagement of spent mushRoom subsTrate to lead the MUSHROOM sector towards a circular economy 2018-08-01 2021-01-31 H2020-EIC-FTI-2018-2020 0 false - Fast Track to Innovation (FTI) + Fast Track to Innovation (FTI) true false - Waste from animal breeding and agriculture, specifically - horse and chicken manure and wheat straw, are the raw materials - of the growing substrate of mushroom. To grow 1 tonne of - mushroom, 3 to 4 tonnes of substrate are needed. However, when - mushroom production is completed the substrate cannot be used for - another growing cycle due to the depletion of nutrients needed - for mushroom growing and it is called Spent Mushroom Substrate - (SMS) and becomes a waste that should be managed according to - regulations. In Europe, c.a. 3.65 million tons of SMS are - generated each year. SMS is a high-moisture content bulk material - rich in organic matter and nutrients and it could be reused in - agriculture by adding it to the soils as amendment or mulch or - weathered to be reused as casing soil. However, nitrates - directive set a disposal limit that makes that large quantities - of SMS cannot be simply spread in soils next to growers’ - facilities, as there is a high risk of leachates and water - pollution. Due to its low bulk density and high water content, - transportation costs are high and therefore storage is becoming a - sound problem. SmartMUSHROOM aims to increase mushroom growers’ - waste management efficiency by using a new technology which allow - them to obtain enough biogas from fresh SMS to dry a mixture of - digestate and additional fresh SMS and pelletize it targeting to - obtain a marketable high-quality organic fertilizer rich in - organic matter and in nutrients, easy to handle, store and - transport to any farming region in Europe. A perfect example of - biobased circular economy. The aim of the project is to build a - pilot plant to demonstrate the technology and find the best - commercial formulation for the pellets to enter organic farming - market. After the end of project we aim to build at least 18 - treatment plants that will place in market 153,000 tonnes of - SMS-pellets, generating a total Turnover of 54M€ in the period - 2021-2025 and up to 105 related new jobs. + Waste from animal breeding and agriculture, specifically horse and chicken manure and wheat straw, are the raw materials of the growing substrate of mushroom. To grow 1 tonne of mushroom, 3 to 4 tonnes of substrate are needed. However, when mushroom production is completed the substrate cannot be used for another growing cycle due to the depletion of nutrients needed for mushroom growing and it is called Spent Mushroom Substrate (SMS) and becomes a waste that should be managed according to regulations. In Europe, c.a. 3.65 million tons of SMS are generated each year. SMS is a high-moisture content bulk material rich in organic matter and nutrients and it could be reused in agriculture by adding it to the soils as amendment or mulch or weathered to be reused as casing soil. However, nitrates directive set a disposal limit that makes that large quantities of SMS cannot be simply spread in soils next to growers’ facilities, as there is a high risk of leachates and water pollution. Due to its low bulk density and high water content, transportation costs are high and therefore storage is becoming a sound problem. SmartMUSHROOM aims to increase mushroom growers’ waste management efficiency by using a new technology which allow them to obtain enough biogas from fresh SMS to dry a mixture of digestate and additional fresh SMS and pelletize it targeting to obtain a marketable high-quality organic fertilizer rich in organic matter and in nutrients, easy to handle, store and transport to any farming region in Europe. A perfect example of biobased circular economy. The aim of the project is to build a pilot plant to demonstrate the technology and find the best commercial formulation for the pellets to enter organic farming market. After the end of project we aim to build at least 18 treatment plants that will place in market 153,000 tonnes of SMS-pellets, generating a total Turnover of 54M€ in the period 2021-2025 and up to 105 related new jobs. EUR 2977940.0 2264140.0 @@ -356,7 +315,7 @@ ec__________::EC::H2020 H2020 Horizon 2020 Framework Programme - + ec:h2020fundings @@ -367,75 +326,69 @@ false 0.900 null - + - pending_org_::39d5641e14f7cfe56e3e836199e33eba + provenanceaction="sysimport:crosswalk:entityregistry"> + pending_org_::39d5641e14f7cfe56e3e836199e33eba ECOBELIEVE DOO - + ECOBELIEVE DOO GRKINJA - pending_org_::3c128a988f1404fed53e1cd8a61df51b - Asociacion Profesional de Productores de Compost y - Hongos, de la Rioja, Navarra y Aragón - + provenanceaction="sysimport:crosswalk:entityregistry"> + pending_org_::3c128a988f1404fed53e1cd8a61df51b + Asociacion Profesional de Productores de Compost y Hongos, de la Rioja, Navarra y Aragón + ASOCHAMP - pending_org_::7555b3c59a033e789ce6190a6c9f39aa - INVESTIGACION Y DESARROLLO CASTILLA Y LEON S.A - - + provenanceaction="sysimport:crosswalk:entityregistry"> + pending_org_::7555b3c59a033e789ce6190a6c9f39aa + INVESTIGACION Y DESARROLLO CASTILLA Y LEON S.A + IDECAL S.A. - pending_org_::4193497542c7b30e3f50f52414905579 + provenanceaction="sysimport:crosswalk:entityregistry"> + pending_org_::4193497542c7b30e3f50f52414905579 NOVIS GMBH - + NOVIS GMBH - + -
    +
    nwo_________::c091653b5930a5a11c748720167b04d7 2016-06-23 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nwo_________::2300148817 2300148817 Production of therapeutic proteins in mushroom @@ -451,8 +404,7 @@ nwo_________::NWO NWO - Netherlands Organisation for Scientific Research (NWO) - + Netherlands Organisation for Scientific Research (NWO) NL @@ -461,32 +413,32 @@ false 0.900 null - + - - + + -
    +
    nwo_________::5ee5a31d8c77215faef3bd35bd9696ff 2016-06-23 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nwo_________::2300148209 2300148209 Control of Verticillium fungicola on mushroom @@ -502,8 +454,7 @@ nwo_________::NWO NWO - Netherlands Organisation for Scientific Research (NWO) - + Netherlands Organisation for Scientific Research (NWO) NL @@ -512,32 +463,32 @@ false 0.900 null - + - - + + -
    +
    nwo_________::98df9c76cbd6537553284af850398659 2016-06-23 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nwo_________::2300147728 2300147728 Master switches of initiation of mushroom formation @@ -553,8 +504,7 @@ nwo_________::NWO NWO - Netherlands Organisation for Scientific Research (NWO) - + Netherlands Organisation for Scientific Research (NWO) NL @@ -563,36 +513,35 @@ false 0.900 null - + - - + + -
    +
    nwo_________::7ddc7f2f259735f312a27c54f6b2ee5d 2016-06-23 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nwo_________::2300164658 2300164658 - Push the white button; controlling mushroom formation - + Push the white button; controlling mushroom formation 2011-09-01 0 false @@ -604,8 +553,7 @@ nwo_________::NWO NWO - Netherlands Organisation for Scientific Research (NWO) - + Netherlands Organisation for Scientific Research (NWO) NL @@ -614,32 +562,32 @@ false 0.900 null - + - - + + -
    +
    nsf_________::c5fa29db776dc4f21919e12cbaea37eb 2016-03-11 2018-08-07
    - + xsi:schemaLocation="http://namespace.openaire.eu/oaf https://www.openaire.eu/schema/1.0/oaf-1.0.xsd"> - + nsf_________::6112554 6112554 Respiratory Mechanisms in Cultivated Mushroom @@ -664,30 +612,26 @@ false 0.900 null - + - openorgs____::2f4b2e4dcb319a5f66e887d2fd555734 - + provenanceaction="sysimport:crosswalk:entityregistry"> + openorgs____::2f4b2e4dcb319a5f66e887d2fd555734 + University of Delaware UD - + - - - + \ No newline at end of file From f6e23f2061bd385a0a190b26bb4feeaf707c4649 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 28 Sep 2021 19:02:51 +0200 Subject: [PATCH 0343/1254] Implement community feedbacks --- .../dspace/content/BitstreamServiceImpl.java | 15 +--- .../content/service/BitstreamService.java | 2 +- .../bitstore/BitstreamStorageServiceImpl.java | 30 +++++--- .../org/dspace/versioning/VersionHistory.java | 4 +- .../impl/CanCreateVersionFeature.java | 1 + .../repository/ItemVersionLinkRepository.java | 3 +- ...orOfAInprogressSubmissionInformations.java | 35 +++++++-- ...ionRestPatchPermissionEvaluatorPlugin.java | 2 + .../dspace/app/rest/ItemRestRepositoryIT.java | 71 ++++++++++++++++++- .../rest/VersionHistoryRestRepositoryIT.java | 32 +++++---- .../app/rest/VersionRestRepositoryIT.java | 70 +++++++++++++++--- .../CanCreateVersionFeatureIT.java | 11 --- .../CanDeleteVersionFeatureIT.java | 4 -- .../CanEditVersionFeatureIT.java | 4 -- .../CanManageVersionsFeatureIT.java | 4 -- dspace/config/modules/versioning.cfg | 4 ++ 16 files changed, 211 insertions(+), 81 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index b97a396e68..071bf3972f 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -97,7 +97,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp @Override public Bitstream clone(Context context, Bitstream bitstream) - throws SQLException { + throws SQLException, AuthorizeException { // Create a new bitstream with a new ID. Bitstream clonedBitstream = bitstreamDAO.create(context, new Bitstream()); // Set the internal identifier, file size, checksum, and @@ -107,18 +107,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp clonedBitstream.setChecksum(bitstream.getChecksum()); clonedBitstream.setChecksumAlgorithm(bitstream.getChecksumAlgorithm()); clonedBitstream.setFormat(bitstream.getBitstreamFormat()); - - try { - //Update our bitstream but turn off the authorization system since permissions - //haven't been set at this point in time. - context.turnOffAuthorisationSystem(); - update(context, clonedBitstream); - } catch (AuthorizeException e) { - log.error(e); - //Can never happen since we turn off authorization before we update - } finally { - context.restoreAuthSystemState(); - } + update(context, clonedBitstream); return clonedBitstream; } diff --git a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java index cf7df9c489..4621c95e7c 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java @@ -54,7 +54,7 @@ public interface BitstreamService extends DSpaceObjectService, DSpace * @return the clone * @throws SQLException if database error */ - public Bitstream clone(Context context, Bitstream bitstream) throws SQLException; + public Bitstream clone(Context context, Bitstream bitstream) throws SQLException, AuthorizeException; /** * Create a new bitstream, with a new ID. The checksum and file size are 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 de1f671ca5..8bf5d3cbd3 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 @@ -350,20 +350,30 @@ public class BitstreamStorageServiceImpl implements BitstreamStorageService, Ini */ @Override public Bitstream clone(Context context, Bitstream bitstream) throws SQLException, IOException, AuthorizeException { - Bitstream clonedBitstream = bitstreamService.clone(context, bitstream); - clonedBitstream.setStoreNumber(bitstream.getStoreNumber()); + Bitstream clonedBitstream = null; + try { + // Update our bitstream but turn off the authorization system since permissions + // haven't been set at this point in time. + context.turnOffAuthorisationSystem(); + clonedBitstream = bitstreamService.clone(context, bitstream); + clonedBitstream.setStoreNumber(bitstream.getStoreNumber()); - List metadataValues = bitstreamService - .getMetadata(bitstream, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + List metadataValues = bitstreamService.getMetadata(bitstream, Item.ANY, Item.ANY, Item.ANY, + Item.ANY); - for (MetadataValue metadataValue : metadataValues) { - bitstreamService.addMetadata(context, clonedBitstream, metadataValue.getMetadataField(), - metadataValue.getLanguage(), metadataValue.getValue(), metadataValue.getAuthority(), - metadataValue.getConfidence()); + for (MetadataValue metadataValue : metadataValues) { + bitstreamService.addMetadata(context, clonedBitstream, metadataValue.getMetadataField(), + metadataValue.getLanguage(), metadataValue.getValue(), metadataValue.getAuthority(), + metadataValue.getConfidence()); + } + bitstreamService.update(context, clonedBitstream); + } catch (AuthorizeException e) { + log.error(e); + // Can never happen since we turn off authorization before we update + } finally { + context.restoreAuthSystemState(); } - bitstreamService.update(context, clonedBitstream); return clonedBitstream; - } /** diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java index f7d0115e3a..231ccc29d9 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersionHistory.java @@ -21,7 +21,7 @@ import javax.persistence.OrderBy; import javax.persistence.SequenceGenerator; import javax.persistence.Table; -import com.amazonaws.util.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; import org.hibernate.proxy.HibernateProxyHelper; @@ -94,7 +94,7 @@ public class VersionHistory implements ReloadableEntity { * @return true if the last version in submission, otherwise false. */ public boolean hasDraftVersion() { - if (!CollectionUtils.isNullOrEmpty(versions) && Objects.nonNull(versions.get(0).getItem())) { + if (CollectionUtils.isNotEmpty(versions) && Objects.nonNull(versions.get(0).getItem())) { return !versions.get(0).getItem().isArchived(); } return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java index 4dbc3cdeeb..d75c441533 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java @@ -55,6 +55,7 @@ public class CanCreateVersionFeature implements AuthorizationFeature { } Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); if (Objects.nonNull(item)) { + // The property versioning.block.entity is used to disable versioning for items with EntityType boolean isBlockEntity = configurationService.getBooleanProperty("versioning.block.entity", true); boolean hasEntityType = StringUtils.isNotBlank(itemService. getMetadataFirstValue(item, "dspace", "entity", "type", Item.ANY)); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java index 7289745b1c..364ac1725a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java @@ -49,7 +49,8 @@ public class ItemVersionLinkRepository extends AbstractDSpaceRestRepository * itemUuid param as UUID * @throws SQLException If something goes wrong */ - @PreAuthorize("hasPermission(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid), 'VERSION', 'READ')") + @PreAuthorize("hasPermission(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid), 'VERSION', 'READ') || " + + "@extractorOf.getVersionIdByItemUUID(#request, #itemUuid) == null" ) public VersionRest getItemVersion(@Nullable HttpServletRequest request, UUID itemUuid, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java index 559349d1af..c4d6667ecb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java @@ -60,6 +60,16 @@ public class ExtractorOfAInprogressSubmissionInformations { @Autowired private RequestService requestService; + /** + * This method is used on security checks, given a versionHistory id, + * it searches the latest version and checks if there is + * a Workspace/Workflow item in progress submission, + * if yes return the id of this one, otherwise returns null. + * + * @param request The current request + * @param versionHistoryId VersionHistoryId + * @return + */ public Integer getAInprogressSubmissionID(@Nullable HttpServletRequest request, Integer versionHistoryId) { Context context = getContext(request); if (Objects.nonNull(versionHistoryId)) { @@ -67,11 +77,11 @@ public class ExtractorOfAInprogressSubmissionInformations { VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); if (Objects.nonNull(versionHistory)) { Version oldestVersion = versionHistoryService.getLatestVersion(context, versionHistory); - WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); if (Objects.nonNull(workspaceItem)) { return workspaceItem.getID(); } + WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); if (Objects.nonNull(workflowItem)) { return workflowItem.getID(); } @@ -83,6 +93,15 @@ public class ExtractorOfAInprogressSubmissionInformations { return null; } + /** + * This method is used on security checks, given a versionHistory id, + * it searches the latest version and checks if there is a Workspace/Workflow item in progress submission, + * if yes return the rest name - 'workspaceitem' or 'workflowitem', otherwise it returns the empty string. + * + * @param request The current request + * @param versionHistoryId VersionHistoryId + * @return + */ public String getAInprogressSubmissionTarget(@Nullable HttpServletRequest request, Integer versionHistoryId) { Context context = getContext(request); if (Objects.nonNull(versionHistoryId)) { @@ -90,12 +109,10 @@ public class ExtractorOfAInprogressSubmissionInformations { VersionHistory versionHistory = versionHistoryService.find(context, versionHistoryId); if (Objects.nonNull(versionHistory)) { Version oldestVersion = versionHistoryService.getLatestVersion(context, versionHistory); - WorkflowItem workflowItem = workflowItemService.findByItem(context, oldestVersion.getItem()); - WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, oldestVersion.getItem()); - if (Objects.nonNull(workspaceItem)) { + if (Objects.nonNull(workspaceItemService.findByItem(context, oldestVersion.getItem()))) { return WorkspaceItemRest.NAME; } - if (Objects.nonNull(workflowItem)) { + if (Objects.nonNull(workflowItemService.findByItem(context, oldestVersion.getItem()))) { return WorkflowItemRest.NAME; } } @@ -106,6 +123,14 @@ public class ExtractorOfAInprogressSubmissionInformations { return StringUtils.EMPTY; } + /** + * This method is used on security checks, given an item UUID, + * it searches the relative version, if version exist return its id, otherwise returns null. + * + * @param request The current request + * @param uuid Item uuid + * @return + */ public Integer getVersionIdByItemUUID(@Nullable HttpServletRequest request, UUID uuid) { Context context = getContext(request); if (Objects.nonNull(uuid)) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java index 84490df457..04802e5b75 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersionRestPatchPermissionEvaluatorPlugin.java @@ -26,6 +26,8 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; /** + * This class evaluate ADMIN permissions to patch operation over a Version. + * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ @Component diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index e37aca0877..56d8cebeb2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -4175,8 +4175,6 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/core/items/" + item.getID() + "/version")) .andExpect(status().isUnauthorized()); - - configurationService.setProperty("versioning.item.history.view.admin", true); } @Test @@ -4207,8 +4205,47 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { String epersonToken = getAuthToken(eperson.getEmail(), password); getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/version")) .andExpect(status().isForbidden()); + } - configurationService.setProperty("versioning.item.history.view.admin", true); + @Test + public void findVersionForItemAndProprtyHistoryViewAdminIsDisabledTest() throws Exception { + configurationService.setProperty("versioning.item.history.view.admin", false); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + VersionBuilder.createVersion(context, item, "test").build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/version")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(1)), + hasJsonPath("$.summary", emptyOrNullString()), + hasJsonPath("$.type", is("version")) + ))) + .andExpect(jsonPath("$._links.versionhistory.href", + Matchers.containsString("api/versioning/versions/1/versionhistory"))) + .andExpect(jsonPath("$._links.item.href", + Matchers.containsString("api/versioning/versions/1/item"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("api/versioning/versions/1"))); } @Test @@ -4242,4 +4279,32 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isBadRequest()); } + @Test + public void findVersionForItemWithoutVersionsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + item.setSubmitter(eperson); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/version")) + .andExpect(status().isNoContent()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java index dc83ae461e..5a0f1aaee1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java @@ -10,6 +10,7 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsInRelativeOrder; 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; @@ -43,6 +44,7 @@ import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -93,6 +95,13 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio context.restoreAuthSystemState(); } + @After + public void cleanup() throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + versionHistoryService.delete(context, versionHistory); + context.restoreAuthSystemState(); + } + @Test public void findOnePublicVersionHistoryTest() throws Exception { getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID())) @@ -135,9 +144,6 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.draftVersion", Matchers.is(true))) .andExpect(jsonPath("$", is(VersionHistoryMatcher.matchEntry(versionHistory)))); - - configurationService.setProperty("versioning.item.history.view.admin", false); - } @Test @@ -147,8 +153,6 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/versioning/versionhistories/" + versionHistory.getID())) .andExpect(status().isForbidden()); - - configurationService.setProperty("versioning.item.history.view.admin", false); } @Test @@ -184,17 +188,17 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio .withSubject("ExtraEntry") .build(); - Version version = VersionBuilder.createVersion(context, item, "test").build(); + Version version2 = VersionBuilder.createVersion(context, item, "test").build(); VersionHistory versionHistory = versionHistoryService.findByItem(context, item); - Version version2 = versioningService.getVersion(context, item); + Version version = versioningService.getVersion(context, item); context.turnOffAuthorisationSystem(); String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", containsInAnyOrder( - VersionMatcher.matchEntry(version), - VersionMatcher.matchEntry(version2) + .andExpect(jsonPath("$._embedded.versions", containsInRelativeOrder( + VersionMatcher.matchEntry(version2), + VersionMatcher.matchEntry(version) ))); context.turnOffAuthorisationSystem(); @@ -203,10 +207,10 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.versions", containsInAnyOrder( - VersionMatcher.matchEntry(version), + .andExpect(jsonPath("$._embedded.versions", containsInRelativeOrder( + VersionMatcher.matchEntry(version3), VersionMatcher.matchEntry(version2), - VersionMatcher.matchEntry(version3) + VersionMatcher.matchEntry(version) ))); } @@ -235,8 +239,6 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) .andExpect(status().isUnauthorized()); - - configurationService.setProperty("versioning.item.history.view.admin", true); } @Test 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 916aca0198..94f5f988bc 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 @@ -18,12 +18,15 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; +import org.apache.commons.io.IOUtils; import org.dspace.app.rest.authorization.Authorization; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureService; @@ -40,6 +43,8 @@ import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; +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; @@ -47,6 +52,7 @@ import org.dspace.builder.ItemBuilder; import org.dspace.builder.VersionBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -503,7 +509,8 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { hasJsonPath("$.summary", is("check first version")), hasJsonPath("$.submitterName", is("first (admin) last (admin)")), hasJsonPath("$.type", is("version")) - ))); + ))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); } finally { VersionBuilder.delete(idRef.get()); } @@ -654,7 +661,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { } finally { VersionBuilder.delete(idRef.get()); } - configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); } @Test @@ -717,8 +723,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) .content("/api/core/items/" + itemA.getID())) .andExpect(status().isForbidden()); - - configurationService.setProperty("versioning.block.entity", ""); } @Test @@ -762,8 +766,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { } finally { VersionBuilder.delete(idRef.get()); } - - configurationService.setProperty("versioning.block.entity", ""); } @Test @@ -809,8 +811,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { } finally { VersionBuilder.delete(idRef.get()); } - configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); - configurationService.setProperty("versioning.block.entity", ""); } @Test @@ -1434,4 +1434,58 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { Matchers.is(AuthorizationMatcher.matchAuthorization(admin2ItemA)))); } + @Test + public void createFirstVersionItemWithBitstreamBySubmitterTest() throws Exception { + configurationService.setProperty("versioning.submitterCanCreateNewVersion", true); + context.turnOffAuthorisationSystem(); + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, rootCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + + Item itemA = ItemBuilder.createItem(context, col) + .withTitle("Public item") + .withIssueDate("2021-04-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + itemA.setSubmitter(eperson); + + Bundle bundle = BundleBuilder.createBundle(context, itemA).withName("Bundle 0").build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + + try (InputStream is = IOUtils.toInputStream(bitstreamContent, StandardCharsets.UTF_8)) { + BitstreamBuilder.createBitstream(context, bundle, is) + .withName("Bitstream0") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + String epersonToken = getAuthToken(eperson.getEmail(), password); + try { + getClient(epersonToken).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + itemA.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"))); + } finally { + VersionBuilder.delete(idRef.get()); + } + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java index 8a6d836466..81302d891c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java @@ -144,8 +144,6 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", greaterThan(0))) .andExpect(jsonPath("$._embedded").exists()); - - configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); } @Test @@ -214,8 +212,6 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest getClient(tokenUser).perform(get("/api/authz/authorizations/" + user2ItemB.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.submitterCanCreateNewVersion", false); } @Test @@ -351,8 +347,6 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2ItemA.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); } @Test @@ -400,8 +394,6 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2ItemA.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); } @Test @@ -449,9 +441,6 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + eperson2ItemA.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); - configurationService.setProperty("versioning.submitterCanCreateNewVersion", true); } } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java index 08cdc51108..0f09380d70 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java @@ -304,8 +304,6 @@ public class CanDeleteVersionFeatureIT extends AbstractControllerIntegrationTest getClient().perform(get("/api/authz/authorizations/" + anonymous2ItemA.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); } @Test @@ -356,8 +354,6 @@ public class CanDeleteVersionFeatureIT extends AbstractControllerIntegrationTest getClient().perform(get("/api/authz/authorizations/" + anonymous2ItemA.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); } } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java index 2aa2356ce5..4e6339aabd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java @@ -261,8 +261,6 @@ public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToVersion.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); } @Test @@ -343,8 +341,6 @@ public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/authz/authorizations/" + anonymousToVersion.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); } } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java index fe4c7cfa45..f11017449a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java @@ -290,8 +290,6 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes getClient(tokenAdminCol2).perform(get("/api/authz/authorizations/" + adminOfCol2ToItemA.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); } @Test @@ -383,8 +381,6 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes getClient(tokenAdminCol2).perform(get("/api/authz/authorizations/" + adminOfCol2ToItemA.getID())) .andExpect(status().isNotFound()); - - configurationService.setProperty("versioning.block.entity", ""); } } \ No newline at end of file diff --git a/dspace/config/modules/versioning.cfg b/dspace/config/modules/versioning.cfg index 982034c530..2c4b82dd85 100644 --- a/dspace/config/modules/versioning.cfg +++ b/dspace/config/modules/versioning.cfg @@ -16,3 +16,7 @@ versioning.item.history.include.submitter=false # If you want to allow submitters to create new versions of there items, set # the property submitterCanCreateNewVersion true. # versioning.submitterCanCreateNewVersion=false + +# The property versioning.block.entity is used to disable versioning +# for items with EntityType, the default value is true if it unset. +# versioning.block.entity= \ No newline at end of file From a41574dbdbd40c01ec2d7fe712cd34a1dd040d7f Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 28 Sep 2021 23:12:58 +0200 Subject: [PATCH 0344/1254] fix locale setting in the obtainContext method --- .../main/java/org/dspace/app/rest/utils/ContextUtil.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java index be8f49bad2..93ef8bb302 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java @@ -74,8 +74,6 @@ public class ContextUtil { if (context == null) { try { context = ContextUtil.initializeContext(); - Locale currentLocale = getLocale(context, request); - context.setCurrentLocale(currentLocale); } catch (SQLException e) { log.error("Unable to initialize context", e); return null; @@ -84,7 +82,11 @@ public class ContextUtil { // Store the context in the request request.setAttribute(DSPACE_CONTEXT, context); } - + // this need to be verified each time that the context is extracted from the request + // as some call happen before that the login process is completed and user settings can + // change the locale + Locale currentLocale = getLocale(context, request); + context.setCurrentLocale(currentLocale); return context; } From ea61bc3b83824a465ffe605a80df434bee5baf7c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 28 Sep 2021 23:30:17 +0200 Subject: [PATCH 0345/1254] Add test to proof behavior with restricted items --- .../app/rest/iiif/IIIFControllerIT.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) 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 0929d481ba..1fdf58e2e3 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 @@ -21,10 +21,14 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.hamcrest.Matchers; import org.junit.Test; @@ -822,6 +826,83 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.service").doesNotExist()); } + @Test + public void findOneIIIFRestrictedItem() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .build(); + + Group staffGroup = GroupBuilder.createGroup(context) + .withName("Staff") + .build(); + Group anotherGroup = GroupBuilder.createGroup(context) + .withName("anotherGroup") + .build(); + + Item restrictedItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Restricted item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .enableIIIF() + .withIIIFCanvasWidth(2000) + .withIIIFCanvasHeight(3000) + .withIIIFCanvasNaming("Global") + .enableIIIFSearch() + .withReaderGroup(staffGroup) + .build(); + + EPerson staffEperson = EPersonBuilder.createEPerson(context).withEmail("staff@example.com") + .withPassword(password).withGroupMembership(staffGroup).build(); + String bitstreamContent = "ThisIsSomeText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, restrictedItem1, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .withIIIFLabel("Custom Label") + .withIIIFCanvasWidth(3163) + .withIIIFCanvasHeight(4220) + .withReaderGroup(staffGroup) + .build(); + } + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + BitstreamBuilder + .createBitstream(context, restrictedItem1, is) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withReaderGroup(anotherGroup) + .build(); + } + context.restoreAuthSystemState(); + // anonymous cannot get the iiif manifest for the restricted item + getClient().perform(get("/iiif/" + restrictedItem1.getID() + "/manifest")) + .andExpect(status().isUnauthorized()); + // not authorized eperson cannot get the iiif manifest + getClient(getAuthToken(eperson.getEmail(), password)) + .perform(get("/iiif/" + restrictedItem1.getID() + "/manifest")).andExpect(status().isForbidden()); + // authorized eperson get the full manifest including canvas related to not accessible bitstreams + // access to the bitstream is eventually denied/granted via the IIIF server for a downgraded image + // Expect canvas label, width and height to match bitstream description. + getClient(getAuthToken(staffEperson.getEmail(), password)) + .perform(get("/iiif/" + restrictedItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].@id", + Matchers.containsString("/iiif/" + restrictedItem1.getID() + "/canvas/c0"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Custom Label"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(3163))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(4220))) + .andExpect(jsonPath("$.sequences[0].canvases[1].@id", + Matchers.containsString("/iiif/" + restrictedItem1.getID() + "/canvas/c1"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Global 2"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].width", is(2000))) + .andExpect(jsonPath("$.sequences[0].canvases[1].height", is(3000))) + .andExpect(jsonPath("$.structures").doesNotExist()) + .andExpect(jsonPath("$.service").exists()); + } @Test public void findOneCanvas() throws Exception { From 0c48606ebe39aa90b91c45867dadd75511d59985 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 28 Sep 2021 16:00:03 -0700 Subject: [PATCH 0346/1254] Added null check in canvas service to prevent exception in iiif metadata model. --- .../java/org/dspace/app/rest/iiif/service/CanvasService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index 763cd6b47a..f4b1553159 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -145,7 +145,9 @@ public class CanvasService extends AbstractResourceService { Item.ANY); List values = new ArrayList(); for (MetadataValue meta : metadata) { - values.add(meta.getValue()); + if (meta.getValue() != null) { + values.add(meta.getValue()); + } } if (values.size() > 0) { if (values.size() > 1) { From aa8c64f8ab586a46d00bd691c257d1edfa1d0c3a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 28 Sep 2021 16:09:30 -0700 Subject: [PATCH 0347/1254] Added todo notice to cache configuration class. --- .../main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java index ffd0f81125..9134a65432 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java @@ -14,6 +14,8 @@ import org.springframework.context.annotation.Configuration; * Enables Spring cache support. The configuration file is defined in * application properties. *

    spring.cache.jcache.config=classpath:iiif/cache/ehcache.xml

    + * TODO: Before the cache is used in production there must be a way to + * evict from the cache whenever a dspace item, bundle or bitstream is changed. */ @Configuration @EnableCaching From bdbafee72c2ab60abdb605a2fca750691b9b967e Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 29 Sep 2021 11:42:13 +0200 Subject: [PATCH 0348/1254] fix failed tests --- .../dspace/app/rest/ItemRestRepositoryIT.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 56d8cebeb2..2c45ac7daf 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -80,6 +80,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; +import org.dspace.versioning.service.VersioningService; import org.dspace.workflow.WorkflowItem; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -89,6 +90,9 @@ import org.springframework.test.web.servlet.MvcResult; public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private VersioningService versioningService; + @Autowired private CollectionService collectionService; @@ -3853,7 +3857,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(tokenEperson).perform(get("/api/core/items/" + item.getID()) .param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) + .andExpect(jsonPath("$", HalMatcher.matchEmbeds("version"))) .andExpect(jsonPath("$.uuid", Matchers.is(item.getID().toString()))) .andExpect(jsonPath("$.name", Matchers.is(item.getName()))) .andExpect(jsonPath("$.handle", Matchers.is(item.getHandle()))) @@ -3884,7 +3888,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/core/items/" + item.getID()) .param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) + .andExpect(jsonPath("$", HalMatcher.matchEmbeds("version"))) .andExpect(jsonPath("$.uuid", Matchers.is(item.getID().toString()))) .andExpect(jsonPath("$.name", Matchers.is(item.getName()))) .andExpect(jsonPath("$.handle", Matchers.is(item.getHandle()))) @@ -4229,6 +4233,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); VersionBuilder.createVersion(context, item, "test").build(); + Version v1 = versioningService.getVersion(context, item); context.restoreAuthSystemState(); @@ -4240,12 +4245,12 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { hasJsonPath("$.summary", emptyOrNullString()), hasJsonPath("$.type", is("version")) ))) - .andExpect(jsonPath("$._links.versionhistory.href", - Matchers.containsString("api/versioning/versions/1/versionhistory"))) - .andExpect(jsonPath("$._links.item.href", - Matchers.containsString("api/versioning/versions/1/item"))) - .andExpect(jsonPath("$._links.self.href", - Matchers.containsString("api/versioning/versions/1"))); + .andExpect(jsonPath("$._links.versionhistory.href", Matchers + .containsString("api/versioning/versions/" + v1.getID() + "/versionhistory"))) + .andExpect(jsonPath("$._links.item.href", Matchers + .containsString("api/versioning/versions/" + v1.getID() + "/item"))) + .andExpect(jsonPath("$._links.self.href", Matchers + .containsString("api/versioning/versions/" + v1.getID() ))); } @Test From 9f237b11882c8796cdba6acaf26b3c53e54230fc Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 29 Sep 2021 12:00:48 +0200 Subject: [PATCH 0349/1254] added test to prove pagination bug --- .../RelationshipTypeRestRepositoryIT.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java index f32e162b53..a2ed672a61 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java @@ -288,4 +288,38 @@ public class RelationshipTypeRestRepositoryIT extends AbstractEntityIntegrationT .andExpect(status().isBadRequest()); } + @Test + public void findByEntityTypePublicationPaginationTest() throws Exception { + getClient().perform(get("/api/core/relationshiptypes/search/byEntityType") + .param("type", "Publication") + .param("size", "3")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isAuthorOfPublication", "isPublicationOfAuthor"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isProjectOfPublication", "isPublicationOfProject"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isOrgUnitOfPublication", "isPublicationOfOrgUnit") + ))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + + getClient().perform(get("/api/core/relationshiptypes/search/byEntityType") + .param("type", "Publication") + .param("page", "1") + .param("size", "3")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isAuthorOfPublication", "isPublicationOfAuthor"), + RelationshipTypeMatcher.matchExplicitRestrictedRelationshipTypeValues( + "isPublicationOfJournalIssue", "isJournalIssueOfPublication") + ))) + .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(5))); + } + } From 330e3f5838592df012bec0fc5c27227db2c49c16 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 29 Sep 2021 12:03:28 +0200 Subject: [PATCH 0350/1254] fix pagination bug and add the JavaDocs --- .../content/RelationshipTypeServiceImpl.java | 5 +++++ .../content/dao/RelationshipTypeDAO.java | 11 +++++++++++ .../dao/impl/RelationshipTypeDAOImpl.java | 17 +++++++++++++++++ .../service/RelationshipTypeService.java | 19 +++++++++++++++++++ .../RelationshipRestRepository.java | 14 ++++++++++++++ .../RelationshipTypeRestRepository.java | 11 ++++++++++- 6 files changed, 76 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java index 29472436bd..9e5de89ae2 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java @@ -174,4 +174,9 @@ public class RelationshipTypeServiceImpl implements RelationshipTypeService { } relationshipTypeDAO.delete(context, relationshipType); } + + @Override + public int countByEntityType(Context context, EntityType entityType) throws SQLException { + return relationshipTypeDAO.countByEntityType(context, entityType); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java index e451e48cf2..457b6baa45 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java @@ -120,4 +120,15 @@ public interface RelationshipTypeDAO extends GenericDAO { List findByEntityType(Context context, EntityType entityType, Boolean isLeft, Integer limit, Integer offset) throws SQLException; + + /** + * Count all relationship types that matches provided EntityType object on any side of relationship + * + * @param context The relevant DSpace context + * @param entityType The EntityType object that will be used to check on + * @return + * @throws SQLException If database error + */ + public int countByEntityType(Context context, EntityType entityType) throws SQLException; + } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java index 96d4bf68fb..798aaa61a8 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java @@ -83,6 +83,7 @@ public class RelationshipTypeDAOImpl extends AbstractHibernateDAO relationshipTypeRoot = criteriaQuery.from(RelationshipType.class); + criteriaQuery.select(relationshipTypeRoot); + criteriaQuery.where( + criteriaBuilder.or(criteriaBuilder. + equal(relationshipTypeRoot.get(RelationshipType_.leftType), entityType), + criteriaBuilder + .equal(relationshipTypeRoot.get(RelationshipType_.rightType), entityType) + ) + ); + return count(context, criteriaQuery, criteriaBuilder, relationshipTypeRoot); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java index 94ebbca41e..676507a0fc 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java @@ -100,9 +100,28 @@ public interface RelationshipTypeService extends DSpaceCRUDService findByEntityType(Context context, EntityType entityType) throws SQLException; + /** + * Returns a list of relationship types that matches provided EntityType object on any side of relationship + * + * @param context The relevant DSpace context + * @param entityType The EntityType object that will be used to check on + * @param limit Paging limit + * @param offset Paging offset + * @return + * @throws SQLException If database error + */ List findByEntityType(Context context, EntityType entityType, Integer limit, Integer offset) throws SQLException; + /** + * Count all relationship types that matches provided EntityType object on any side of relationship + * + * @param context The relevant DSpace context + * @param entityType The EntityType object that will be used to check on + * @return + * @throws SQLException If database error + */ + public int countByEntityType(Context context, EntityType entityType) throws SQLException; /** * This method will return a list of RelationshipType objects for which the given EntityType object is equal diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java index 00dd0c8893..7f2409163f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -371,6 +371,20 @@ public class RelationshipRestRepository extends DSpaceRestRepository findByItemsAndType( @Parameter(value = "typeId", required = true) Integer typeId, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java index ce56143179..a4e65d75a3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java @@ -58,6 +58,14 @@ public class RelationshipTypeRestRepository extends DSpaceRestRepository findByEntityType(@Parameter(value = "type", required = true) String type, Pageable pageable) throws SQLException { @@ -68,7 +76,8 @@ public class RelationshipTypeRestRepository extends DSpaceRestRepository relationshipTypes = relationshipTypeService.findByEntityType(context, entityType, Math.toIntExact(pageable.getPageSize()), Math.toIntExact(pageable.getOffset())); - return converter.toRestPage(relationshipTypes, pageable, utils.obtainProjection()); + int total = relationshipTypeService.countByEntityType(context, entityType); + return converter.toRestPage(relationshipTypes, pageable, total, utils.obtainProjection()); } @Override From d6d2f2748357026e3410c82c5f780e578488ed3f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 29 Sep 2021 13:34:46 +0200 Subject: [PATCH 0351/1254] added javaDoc --- .../org/dspace/content/service/EntityTypeService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java index 38b905fb74..5dd9c357ae 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java @@ -59,6 +59,15 @@ public interface EntityTypeService extends DSpaceCRUDService { */ public EntityType create(Context context, String entityTypeString) throws SQLException, AuthorizeException; + /** + * Retrieves all entity types related to the collections on which the current user can deposit + * + * @param context DSpace context object + * @return + * @throws SQLException If database error + * @throws SolrServerException If there is a problem in communicating with Solr + * @throws IOException If IO error + */ public List getSubmitAuthorizedTypes(Context context) throws SQLException, SolrServerException, IOException; /** From 3fe4e2d17084a4408012e1133e19fa7cf61eb0e3 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 29 Sep 2021 13:36:45 +0200 Subject: [PATCH 0352/1254] fix duplicate method --- .../dspace/content/CollectionServiceImpl.java | 50 +------------------ 1 file changed, 2 insertions(+), 48 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 d81235bb9c..1eba60a532 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -928,7 +928,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); - DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery,community, q); + DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); collections.add(c); @@ -943,56 +943,10 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.setMaxResults(0); discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); - DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery,community,q); + DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q); return (int)resp.getTotalSearchResults(); } - /** - * Finds all Indexed Collections where the current user has submit rights. If the user is an Admin, - * this is all Indexed Collections. Otherwise, it includes those collections where - * an indexed "submit" policy lists either the eperson or one of the eperson's groups - * - * @param context DSpace context - * @param discoverQuery - * @param community parent community, could be null - * @param q limit the returned collection to those with metadata values matching the query - * terms. The terms are used to make also a prefix query on SOLR - * so it can be used to implement an autosuggest feature over the collection name - * @return discovery search result objects - * @throws SQLException if something goes wrong - * @throws SearchServiceException if search error - */ - private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQuery discoverQuery, - Community community, String q) throws SQLException, SearchServiceException { - - StringBuilder query = new StringBuilder(); - EPerson currentUser = context.getCurrentUser(); - if (!authorizeService.isAdmin(context)) { - String userId = ""; - if (currentUser != null) { - userId = currentUser.getID().toString(); - } - query.append("submit:(e").append(userId); - Set groups = groupService.allMemberGroupsSet(context, currentUser); - for (Group group : groups) { - query.append(" OR g").append(group.getID()); - } - query.append(")"); - discoverQuery.addFilterQueries(query.toString()); - } - if (community != null) { - discoverQuery.addFilterQueries("location.comm:" + community.getID().toString()); - } - if (StringUtils.isNotBlank(q)) { - StringBuilder buildQuery = new StringBuilder(); - String escapedQuery = ClientUtils.escapeQueryChars(q); - buildQuery.append(escapedQuery).append(" OR ").append(escapedQuery).append("*"); - discoverQuery.setQuery(buildQuery.toString()); - } - DiscoverResult resp = searchService.search(context, discoverQuery); - return resp; - } - /** * Finds all Indexed Collections where the current user has submit rights. If the user is an Admin, * this is all Indexed Collections. Otherwise, it includes those collections where From 9148fc37d81a4a1d735d895492219ec67524fe1c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 29 Sep 2021 15:57:34 +0200 Subject: [PATCH 0353/1254] 83795: Harvest script fixes --- .../java/org/dspace/app/harvest/Harvest.java | 76 +++++++++++++------ .../org/dspace/app/harvest/HarvestCli.java | 50 ++++++++++++ .../HarvestCliScriptConfiguration.java | 22 ++++++ .../harvest/HarvestScriptConfiguration.java | 4 +- dspace/config/spring/api/scripts.xml | 4 +- dspace/config/spring/rest/scripts.xml | 5 ++ 6 files changed, 131 insertions(+), 30 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/harvest/HarvestCli.java create mode 100644 dspace-api/src/main/java/org/dspace/app/harvest/HarvestCliScriptConfiguration.java diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java index bbc54d49e4..5c02d34756 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java @@ -44,18 +44,19 @@ import org.dspace.utils.DSpace; public class Harvest extends DSpaceRunnable { private HarvestedCollectionService harvestedCollectionService; - private EPersonService ePersonService; + protected EPersonService ePersonService; private CollectionService collectionService; private boolean help; private String command = null; - private String eperson = null; private String collection = null; private String oaiSource = null; private String oaiSetID = null; private String metadataKey = null; private int harvestType = 0; + protected Context context; + public HarvestScriptConfiguration getScriptConfiguration() { return new DSpace().getServiceManager() @@ -69,7 +70,9 @@ public class Harvest extends DSpaceRunnable { collectionService = ContentServiceFactory.getInstance().getCollectionService(); - help = commandLine.hasOption('h'); //TODO copy the old message? + assignCurrentUserInContext(); + + help = commandLine.hasOption('h'); if (commandLine.hasOption('s')) { @@ -93,10 +96,8 @@ public class Harvest extends DSpaceRunnable { if (commandLine.hasOption('P')) { command = "purgeAll"; } - - - if (commandLine.hasOption('e')) { - eperson = commandLine.getOptionValue('e'); + if (commandLine.hasOption('o')) { + command = "reimport"; } if (commandLine.hasOption('c')) { collection = commandLine.getOptionValue('c'); @@ -117,6 +118,28 @@ public class Harvest extends DSpaceRunnable { } } + /** + * This method will assign the currentUser to the {@link Context} variable which is also created in this method. + * The instance of the method in this class will fetch the EPersonIdentifier from this class, this identifier + * was given to this class upon instantiation, it'll then be used to find the {@link EPerson} associated with it + * and this {@link EPerson} will be set as the currentUser of the created {@link Context} + * @throws ParseException If something went wrong with the retrieval of the EPerson Identifier + */ + protected void assignCurrentUserInContext() throws ParseException { + UUID currentUserUuid = this.getEpersonIdentifier(); + try { + this.context = new Context(Context.Mode.BATCH_EDIT); + EPerson eperson = ePersonService.find(context, currentUserUuid); + if (eperson == null) { + super.handler.logError("EPerson not found: " + currentUserUuid); + throw new IllegalArgumentException("Unable to find a user with uuid: " + currentUserUuid); + } + this.context.setCurrentUser(eperson); + } catch (SQLException e) { + handler.handleException("Something went wrong trying to fetch eperson for uuid: " + currentUserUuid, e); + } + } + public void internalRun() throws Exception { if (help) { printHelp(); @@ -133,18 +156,16 @@ public class Harvest extends DSpaceRunnable { return; } - Context context = new Context(Context.Mode.BATCH_EDIT); - if (StringUtils.isBlank(command)) { handler.logError("No parameters specified (run with -h flag for details)"); throw new UnsupportedOperationException("No command specified"); } else if ("run".equals(command)) { // Run a single harvest cycle on a collection using saved settings. - if (collection == null || eperson == null) { + if (collection == null || context.getCurrentUser() == null) { handler.logError("A target collection and eperson must be provided (run with -h flag for details)"); throw new UnsupportedOperationException("A target collection and eperson must be provided"); } - runHarvest(context, collection, eperson); + runHarvest(context, collection); } else if ("start".equals(command)) { // start the harvest loop startHarvester(); @@ -153,7 +174,7 @@ public class Harvest extends DSpaceRunnable { resetHarvesting(context); } else if ("purgeAll".equals(command)) { // purge all collections that are set up for harvesting (obviously for testing purposes only) - if (eperson == null) { + if (context.getCurrentUser() == null) { handler.logError("An eperson must be provided (run with -h flag for details)"); throw new UnsupportedOperationException("An eperson must be provided"); } @@ -164,20 +185,30 @@ public class Harvest extends DSpaceRunnable { "Purging the following collections (deleting items and resetting harvest status): " + harvestedCollection .getCollection().getID().toString()); - purgeCollection(context, harvestedCollection.getCollection().getID().toString(), eperson); + purgeCollection(context, harvestedCollection.getCollection().getID().toString()); } context.complete(); } else if ("purge".equals(command)) { // Delete all items in a collection. Useful for testing fresh harvests. - if (collection == null || eperson == null) { + if (collection == null || context.getCurrentUser() == null) { handler.logError("A target collection and eperson must be provided (run with -h flag for details)"); throw new UnsupportedOperationException("A target collection and eperson must be provided"); } - purgeCollection(context, collection, eperson); + purgeCollection(context, collection); context.complete(); //TODO: implement this... remove all items and remember to unset "last-harvested" settings + } else if ("reimport".equals(command)) { + // Delete all items in a collection. Useful for testing fresh harvests. + if (collection == null || context.getCurrentUser() == null) { + handler.logError("A target collection and eperson must be provided (run with -h flag for details)"); + throw new UnsupportedOperationException("A target collection and eperson must be provided"); + } + purgeCollection(context, collection); + runHarvest(context, collection); + context.complete(); + } else if ("config".equals(command)) { // Configure a collection with the three main settings if (collection == null) { @@ -208,8 +239,8 @@ public class Harvest extends DSpaceRunnable { pingResponder(oaiSource, oaiSetID, metadataKey); } else { handler.logError( - "Your command '" + command + "' was not recoginzed properly (run with -h flag for details)"); - throw new UnsupportedOperationException("\"Your command '\" + command + \"' was not recoginzed properly\""); + "Your command '" + command + "' was not recognized properly (run with -h flag for details)"); + throw new UnsupportedOperationException("Your command '" + command + "' was not recognized properly"); } @@ -288,18 +319,15 @@ public class Harvest extends DSpaceRunnable { /** * Purges a collection of all harvest-related data and settings. All items in the collection will be deleted. + * @param collectionID * - * @param collectionID - * @param email */ - private void purgeCollection(Context context, String collectionID, String email) { + private void purgeCollection(Context context, String collectionID) { handler.logInfo( "Purging collection of all items and resetting last_harvested and harvest_message: " + collectionID); Collection collection = resolveCollection(context, collectionID); try { - EPerson eperson = ePersonService.findByEmail(context, email); - context.setCurrentUser(eperson); context.turnOffAuthorisationSystem(); ItemService itemService = ContentServiceFactory.getInstance().getItemService(); @@ -339,7 +367,7 @@ public class Harvest extends DSpaceRunnable { /** * Run a single harvest cycle on the specified collection under the authorization of the supplied EPerson */ - private void runHarvest(Context context, String collectionID, String email) { + private void runHarvest(Context context, String collectionID) { handler.logInfo("Running: a harvest cycle on " + collectionID); handler.logInfo("Initializing the harvester... "); @@ -359,9 +387,7 @@ public class Harvest extends DSpaceRunnable { try { // Harvest will not work for an anonymous user - EPerson eperson = ePersonService.findByEmail(context, email); handler.logInfo("Harvest started... "); - context.setCurrentUser(eperson); harvester.runHarvest(); context.complete(); } catch (SQLException | AuthorizeException | IOException e) { diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCli.java b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCli.java new file mode 100644 index 0000000000..2ea16e9295 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCli.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.harvest; + +import java.sql.SQLException; + +import org.apache.commons.cli.ParseException; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Test class for harvested collections. + * + * @author Alexey Maslov + */ +public class HarvestCli extends Harvest { + + /** + * This is the overridden instance of the {@link Harvest#assignCurrentUserInContext()} method in the parent class + * {@link Harvest}. + * This is done so that the CLI version of the Script is able to retrieve its currentUser from the -e flag given + * with the parameters of the Script. + * + * @throws ParseException If the e flag was not given to the parameters when calling the script + */ + @Override + protected void assignCurrentUserInContext() throws ParseException { + if (this.commandLine.hasOption('e')) { + String ePersonEmail = this.commandLine.getOptionValue('e'); + this.context = new Context(Context.Mode.BATCH_EDIT); + try { + EPerson ePerson = ePersonService.findByEmail(this.context, ePersonEmail); + if (ePerson == null) { + super.handler.logError("EPerson not found: " + ePersonEmail); + throw new IllegalArgumentException("Unable to find a user with email: " + ePersonEmail); + } + this.context.setCurrentUser(ePerson); + } catch (SQLException e) { + throw new IllegalArgumentException("SQLException trying to find user with email: " + ePersonEmail); + } + } + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCliScriptConfiguration.java new file mode 100644 index 0000000000..9e58b64a62 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCliScriptConfiguration.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.harvest; + +import org.apache.commons.cli.Options; + + +public class HarvestCliScriptConfiguration extends HarvestScriptConfiguration { + + public Options getOptions() { + Options options = super.getOptions(); + options.addOption("e", "eperson", true, + "eperson"); + + return options; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java index 4784c3f0ae..5830cef520 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java @@ -49,9 +49,7 @@ public class HarvestScriptConfiguration extends ScriptConfigu options.addOption("S", "start", false, "start the harvest loop"); options.addOption("R", "reset", false, "reset harvest status on all collections"); options.addOption("P", "purge", false, "purge all harvestable collections"); - - options.addOption("e", "eperson", true, - "eperson"); + options.addOption("o", "reimport", false, "reimport all items in the collection"); options.addOption("c", "collection", true, "harvesting collection (handle or id)"); options.addOption("t", "type", true, diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index a4e91b2fd9..5b830e162b 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -41,8 +41,8 @@ - + - + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 2027b979bf..563c639a10 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -33,4 +33,9 @@ + + + + + From 61a9edfe0526edfcb5402e5069720b569d1ccb18 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 29 Sep 2021 16:34:15 +0200 Subject: [PATCH 0354/1254] restore latest test and fix the bug --- .../dspace/app/rest/repository/ItemVersionLinkRepository.java | 2 +- .../test/java/org/dspace/app/rest/ItemRestRepositoryIT.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java index 364ac1725a..d3117a1d5a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java @@ -50,7 +50,7 @@ public class ItemVersionLinkRepository extends AbstractDSpaceRestRepository * @throws SQLException If something goes wrong */ @PreAuthorize("hasPermission(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid), 'VERSION', 'READ') || " + - "@extractorOf.getVersionIdByItemUUID(#request, #itemUuid) == null" ) + "(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid) == null && hasPermission(#itemUuid,'ITEM','READ'))") public VersionRest getItemVersion(@Nullable HttpServletRequest request, UUID itemUuid, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 2c45ac7daf..ada5e2cbf3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -3857,7 +3857,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(tokenEperson).perform(get("/api/core/items/" + item.getID()) .param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", HalMatcher.matchEmbeds("version"))) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) .andExpect(jsonPath("$.uuid", Matchers.is(item.getID().toString()))) .andExpect(jsonPath("$.name", Matchers.is(item.getName()))) .andExpect(jsonPath("$.handle", Matchers.is(item.getHandle()))) @@ -3888,7 +3888,7 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/core/items/" + item.getID()) .param("projection", "full")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", HalMatcher.matchEmbeds("version"))) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) .andExpect(jsonPath("$.uuid", Matchers.is(item.getID().toString()))) .andExpect(jsonPath("$.name", Matchers.is(item.getName()))) .andExpect(jsonPath("$.handle", Matchers.is(item.getHandle()))) From fb7fd3b279ac5ac2ae055d87560fd7485a5f9e5a Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 29 Sep 2021 13:21:04 -0400 Subject: [PATCH 0355/1254] [DS-3952] Make PUT return codes conform to contract. --- .../dspace/app/rest/repository/RequestItemRepository.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 4f00513ff6..5057403ac9 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 @@ -221,7 +221,8 @@ public class RequestItemRepository @Override @PreAuthorize("isAuthenticated()") public RequestItemRest put(Context context, HttpServletRequest request, - String apiCategory, String model, String token, JsonNode requestBody) { + String apiCategory, String model, String token, JsonNode requestBody) + throws AuthorizeException { RequestItem ri = requestItemService.findByToken(context, token); if (null == ri) { throw new UnprocessableEntityException("Item request not found"); @@ -236,7 +237,7 @@ public class RequestItemRepository authorizer = new RequestItemAuthor("", ""); } if (!authorizer.getEmail().equals(context.getCurrentUser().getEmail())) { - throw new RuntimeException("Not authorized to approve this request"); + throw new AuthorizeException("Not authorized to approve this request"); } // Make the changes From 1f15e032fb2ebe77557d4b3aa97fed64d8acee73 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 29 Sep 2021 16:32:23 -0400 Subject: [PATCH 0356/1254] [DS-3952] Item request tokens should match 32 base-16 digits, not base-36. --- .../src/main/java/org/dspace/app/rest/utils/RegexUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java index 0d4ef99d8e..8739f6b8d5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java @@ -44,6 +44,6 @@ public class RegexUtils { * Regular expression to accept a string of 32 hexadecimal digits. */ public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_HEX32 - = "/{id:[0-9a-zA-Z]{32}}"; + = "/{id:[0-9a-fA-F]{32}}"; } From 1cd0cc3c2aee03a73e526c846113d6b02006c0b5 Mon Sep 17 00:00:00 2001 From: Corrado Lombardi Date: Thu, 30 Sep 2021 09:54:10 +0200 Subject: [PATCH 0357/1254] [CST-4507] little code refactoring and updated test behaviour --- .../AuthorizationRestRepository.java | 6 +- .../java/org/dspace/app/rest/utils/Utils.java | 1 - .../rest/AuthorizationRestRepositoryIT.java | 87 ++++++++----------- 3 files changed, 40 insertions(+), 54 deletions(-) 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 3b308a02d6..2f3c284fef 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 @@ -167,7 +167,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository authorizations = findAuthorizationsForUri(context, user, uri, featureName); - if (currUser != user) { + if (ObjectUtils.notEqual(currUser, user)) { // restore the real current user context.restoreContextUser(); } @@ -196,7 +196,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository authorizations = findAuthorizationsByUUIDList(context, type, uuidList, user, featureNames); - if (currUser != user) { + if (ObjectUtils.notEqual(currUser, user)) { // restore the real current user context.restoreContextUser(); } @@ -223,7 +223,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository Date: Thu, 30 Sep 2021 12:16:01 +0200 Subject: [PATCH 0358/1254] [CST-4507] added mandatory parameters and some little improvements in tests readability --- .../AuthorizationRestRepository.java | 4 +- .../rest/AuthorizationRestRepositoryIT.java | 139 ++++++++++-------- 2 files changed, 80 insertions(+), 63 deletions(-) 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 2f3c284fef..05d5a1dde2 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 @@ -176,8 +176,8 @@ public class AuthorizationRestRepository extends DSpaceRestRepository findByObjects(@Parameter(value = "uuid") List uuidList, - @Parameter(value = "type") String type, + public Page findByObjects(@Parameter(value = "uuid", required = true) List uuidList, + @Parameter(value = "type", required = true) String type, @Parameter(value = "eperson") UUID epersonUuid, @Parameter(value = "feature") List featureNames, Pageable pageable) throws AuthorizeException, SQLException { 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 4cef3bbf43..b199a37c96 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 @@ -20,6 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.io.Serializable; import java.util.UUID; +import java.util.function.Supplier; import com.jayway.jsonpath.matchers.JsonPathMatchers; import org.apache.logging.log4j.Logger; @@ -64,6 +65,7 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; /** * Test suite for the Authorization endpoint @@ -1548,7 +1550,8 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration String adminToken = getAuthToken(admin.getEmail(), password); // verify that it works for administrators - with eperson parameter - getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") + + Supplier baseFeatureRequest = () -> get("/api/authz/authorizations/search/objects") .param("type", "core.community") .param("uuid", comId) .param("uuid", secondComId) @@ -1556,10 +1559,13 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration .param("embedLevelDepth", "1") .param("feature", alwaysTrue.getName()) .param("feature", alwaysFalse.getName()) - .param("feature", trueForAdmins.getName()) + .param("feature", trueForLoggedUsers.getName()) + .param("feature", trueForAdmins.getName()); + + getClient(adminToken).perform(baseFeatureRequest.get() .param("eperson", admin.getID().toString())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalElements", is(6))) .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( allOf( hasJsonPath("$.id", is(admin.getID().toString() + "_" + alwaysTrue.getName() + "_" @@ -1575,6 +1581,13 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration hasJsonPath("$._embedded.feature.id", is(trueForAdmins.getName())), hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) ), + allOf( + hasJsonPath("$.id", is(admin.getID().toString() + "_" + trueForLoggedUsers.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId())), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), allOf( hasJsonPath("$.id", is(admin.getID().toString() + "_" + alwaysTrue.getName() + "_" + secondComRest.getUniqueType() + "_" + secondComRest.getId())), @@ -1588,21 +1601,20 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration hasJsonPath("$.type", is("authorization")), hasJsonPath("$._embedded.feature.id", is(trueForAdmins.getName())), hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is(admin.getID().toString() + "_" + trueForLoggedUsers.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId())), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) ) ))); // verify that it works for administrators - without eperson parameter - getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") - .param("type", "core.community") - .param("uuid", comId) - .param("uuid", secondComId) - .param("projection", "level") - .param("embedLevelDepth", "1") - .param("feature", alwaysFalse.getName()) - .param("feature", trueForAdmins.getName()) - .param("feature", alwaysTrue.getName())) + getClient(adminToken).perform(baseFeatureRequest.get()) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalElements", is(6))) .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( allOf( hasJsonPath("$.id", is( @@ -1624,6 +1636,16 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration hasJsonPath("$._embedded.feature.id", is(trueForAdmins.getName())), hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) ), + allOf( + hasJsonPath("$.id", is( + admin.getID().toString() + "_" + + trueForLoggedUsers.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), allOf( hasJsonPath("$.id", is( admin.getID().toString() + "_" @@ -1643,24 +1665,26 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration hasJsonPath("$.type", is("authorization")), hasJsonPath("$._embedded.feature.id", is(trueForAdmins.getName())), hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + admin.getID().toString() + "_" + + trueForLoggedUsers.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(admin.getID().toString())) ) ))); String epersonToken = getAuthToken(eperson.getEmail(), password); // verify that it works for normal loggedin users - with eperson parameter - getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") - .param("type", "core.community") - .param("uuid", comId) - .param("uuid", secondComId) - .param("projection", "level") - .param("embedLevelDepth", "1") - .param("feature", alwaysTrue.getName()) - .param("feature", alwaysFalse.getName()) - .param("feature", trueForAdmins.getName()) + getClient(epersonToken).perform(baseFeatureRequest.get() .param("eperson", eperson.getID().toString())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(4))) .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( allOf( hasJsonPath("$.id", is( @@ -1672,6 +1696,16 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + trueForLoggedUsers.getName() + "_" + + comRest.getUniqueType() + "_" + comRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), allOf( hasJsonPath("$.id", is( eperson.getID().toString() + "_" @@ -1681,19 +1715,21 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration hasJsonPath("$.type", is("authorization")), hasJsonPath("$._embedded.feature.id", is(alwaysTrue.getName())), hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) + ), + allOf( + hasJsonPath("$.id", is( + eperson.getID().toString() + "_" + + trueForLoggedUsers.getName() + "_" + + secondComRest.getUniqueType() + "_" + secondComRest.getId() + )), + hasJsonPath("$.type", is("authorization")), + hasJsonPath("$._embedded.feature.id", is(trueForLoggedUsers.getName())), + hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) ) ))); // verify that it works for normal loggedin users - without eperson parameter - getClient(epersonToken).perform(get("/api/authz/authorizations/search/objects") - .param("type", "core.community") - .param("uuid", comId) - .param("uuid", secondComId) - .param("projection", "level") - .param("embedLevelDepth", "1") - .param("feature", alwaysFalse.getName()) - .param("feature", trueForLoggedUsers.getName()) - .param("feature", alwaysTrue.getName())) + getClient(epersonToken).perform(baseFeatureRequest.get()) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(4))) .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( @@ -1740,16 +1776,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration ))); // verify that it works for administators inspecting other users - by using the eperson parameter - getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") - .param("type", "core.community") - .param("uuid", comId) - .param("uuid", secondComId) - .param("projection", "level") - .param("embedLevelDepth", "1") - .param("feature", alwaysTrue.getName()) - .param("feature", alwaysFalse.getName()) - .param("feature", trueForAdmins.getName()) - .param("feature", trueForLoggedUsers.getName()) + getClient(adminToken).perform(baseFeatureRequest.get() .param("eperson", eperson.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(4))) @@ -1797,16 +1824,8 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration ))); // verify that it works for administators inspecting other users - by assuming login - getClient(adminToken).perform(get("/api/authz/authorizations/search/objects") - .param("type", "core.community") - .param("uuid", comId) - .param("uuid", secondComId) - .param("projection", "level") - .param("embedLevelDepth", "1") - .param("feature", alwaysTrue.getName()) - .param("feature", alwaysFalse.getName()) - .param("feature", trueForAdmins.getName()) - .param("feature", trueForLoggedUsers.getName()) + getClient(adminToken).perform(baseFeatureRequest.get() +// .param("feature", trueForLoggedUsers.getName()) .header("X-On-Behalf-Of", eperson.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(4))) @@ -1854,15 +1873,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration ))); // verify that it works for anonymous users - getClient().perform(get("/api/authz/authorizations/search/objects") - .param("type", "core.community") - .param("uuid", comId) - .param("uuid", secondComId) - .param("projection", "level") - .param("embedLevelDepth", "1") - .param("feature", alwaysFalse.getName()) - .param("feature", trueForAdmins.getName()) - .param("feature", alwaysTrue.getName())) + getClient().perform(baseFeatureRequest.get()) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(2))) .andExpect(jsonPath("$._embedded.authorizations", containsInAnyOrder( @@ -2222,6 +2233,12 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration .param("uuid", UUID.randomUUID().toString()) .param("feature", alwaysTrue.getName())) .andExpect(status().isBadRequest()); + + // verify that returns bad request for missing uuid + getClient().perform(get("/api/authz/authorizations/search/objects") + .param("type", "core.item") + .param("feature", alwaysTrue.getName())) + .andExpect(status().isBadRequest()); } @Test From 16d0059bcd600234cc77a4244d09198130044337 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 30 Sep 2021 12:40:33 +0200 Subject: [PATCH 0359/1254] add support to enable/disable the versioning features via conf --- .../impl/CanCreateVersionFeature.java | 3 + .../impl/CanDeleteVersionFeature.java | 3 + .../impl/CanEditVersionFeature.java | 5 +- .../impl/CanManageVersionsFeature.java | 3 +- .../repository/ItemVersionLinkRepository.java | 5 +- ...sionHistoryDraftVersionLinkRepository.java | 5 +- .../VersionHistoryRestRepository.java | 2 +- .../repository/VersionRestRepository.java | 6 +- .../repository/VersionsLinkRepository.java | 3 +- .../rest/security/VersioningSecurityBean.java | 34 ++++++ .../dspace/app/rest/ItemRestRepositoryIT.java | 37 ++++++ .../rest/VersionHistoryRestRepositoryIT.java | 51 +++++++++ .../app/rest/VersionRestRepositoryIT.java | 106 ++++++++++++++++++ dspace/config/modules/versioning.cfg | 4 + 14 files changed, 254 insertions(+), 13 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersioningSecurityBean.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java index d75c441533..b77dc5f623 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanCreateVersionFeature.java @@ -49,6 +49,9 @@ public class CanCreateVersionFeature implements AuthorizationFeature { @SuppressWarnings("rawtypes") public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof ItemRest) { + if (!configurationService.getBooleanProperty("versioning.enabled", true)) { + return false; + } EPerson currentUser = context.getCurrentUser(); if (Objects.isNull(currentUser)) { return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java index 7b47cfe444..59ea8ae8cb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanDeleteVersionFeature.java @@ -51,6 +51,9 @@ public class CanDeleteVersionFeature extends DeleteFeature { @SuppressWarnings("rawtypes") public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof VersionRest) { + if (!configurationService.getBooleanProperty("versioning.enabled", true)) { + return false; + } Version version = versioningService.getVersion(context, ((VersionRest)object).getId()); if (Objects.nonNull(version) && Objects.nonNull(version.getItem())) { ItemRest itemRest = itemConverter.convert(version.getItem(), DefaultProjection.DEFAULT); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java index ed2d852272..cc767c4e07 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanEditVersionFeature.java @@ -18,7 +18,6 @@ import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.service.VersioningService; @@ -51,8 +50,8 @@ public class CanEditVersionFeature implements AuthorizationFeature { @SuppressWarnings("rawtypes") public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof VersionRest) { - EPerson currentUser = context.getCurrentUser(); - if (Objects.isNull(currentUser)) { + boolean isEnabled = configurationService.getBooleanProperty("versioning.enabled", true); + if (Objects.isNull(context.getCurrentUser()) || !isEnabled) { return false; } Version version = versioningService.getVersion(context, (((VersionRest) object).getId())); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java index 59452accee..5b5e4c16e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanManageVersionsFeature.java @@ -48,7 +48,8 @@ public class CanManageVersionsFeature implements AuthorizationFeature { @SuppressWarnings("rawtypes") public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof ItemRest) { - if (Objects.isNull(context.getCurrentUser())) { + boolean isEnabled = configurationService.getBooleanProperty("versioning.enabled", true); + if (!isEnabled || Objects.isNull(context.getCurrentUser())) { return false; } Item item = itemService.find(context, UUID.fromString(((ItemRest) object).getUuid())); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java index d3117a1d5a..95bbddc665 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemVersionLinkRepository.java @@ -49,8 +49,9 @@ public class ItemVersionLinkRepository extends AbstractDSpaceRestRepository * itemUuid param as UUID * @throws SQLException If something goes wrong */ - @PreAuthorize("hasPermission(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid), 'VERSION', 'READ') || " + - "(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid) == null && hasPermission(#itemUuid,'ITEM','READ'))") + @PreAuthorize("@versioningSecurity.isEnableVersioning() && " + + "(hasPermission(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid), 'VERSION', 'READ') || " + + "(@extractorOf.getVersionIdByItemUUID(#request, #itemUuid) == null && hasPermission(#itemUuid,'ITEM','READ')))") public VersionRest getItemVersion(@Nullable HttpServletRequest request, UUID itemUuid, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java index b0d23a2eb6..21d90ddda0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryDraftVersionLinkRepository.java @@ -49,8 +49,9 @@ public class VersionHistoryDraftVersionLinkRepository extends AbstractDSpaceRest @Autowired(required = true) private WorkflowItemService workflowItemService; - @PreAuthorize("hasPermission(@extractorOf.getAInprogressSubmissionID(#request, #versionHistoryId), " - + "@extractorOf.getAInprogressSubmissionTarget(#request, #versionHistoryId), 'READ')") + @PreAuthorize("@versioningSecurity.isEnableVersioning() && " + + "hasPermission(@extractorOf.getAInprogressSubmissionID(#request, #versionHistoryId), " + + "@extractorOf.getAInprogressSubmissionTarget(#request, #versionHistoryId), 'READ')") public AInprogressSubmissionRest getDraftVersion(@Nullable HttpServletRequest request, Integer versionHistoryId, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java index 6d1175e80b..f41003d17d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionHistoryRestRepository.java @@ -38,7 +38,7 @@ public class VersionHistoryRestRepository extends DSpaceRestRepository stringList) throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { @@ -152,7 +152,7 @@ public class VersionRestRepository extends DSpaceRestRepository getVersions(@Nullable HttpServletRequest request, Integer versionHistoryId, @Nullable Pageable optionalPageable, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersioningSecurityBean.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersioningSecurityBean.java new file mode 100644 index 0000000000..5e117b79af --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/VersioningSecurityBean.java @@ -0,0 +1,34 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 org.dspace.services.ConfigurationService; +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 versioning endpoint + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(value = "versioningSecurity") +public class VersioningSecurityBean { + + @Autowired + private ConfigurationService configurationService; + + /** + * This method checks if the versioning features are enabled + * + * @return true if is enabled or unset + */ + public boolean isEnableVersioning() { + return configurationService.getBooleanProperty("versioning.enabled", true); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index ada5e2cbf3..7a129c3d28 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -4312,4 +4312,41 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isNoContent()); } + @Test + public void findVersionForItemWithoutVersionsWithVersioningDisabledTest() throws Exception { + configurationService.setProperty("versioning.enabled", false); + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + item.setSubmitter(eperson); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/version")) + .andExpect(status().isForbidden()); + + getClient(epersonToken).perform(get("/api/core/items/" + item.getID() + "/version")) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/core/items/" + item.getID() + "/version")) + .andExpect(status().isUnauthorized()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java index 5a0f1aaee1..3ce5393c93 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionHistoryRestRepositoryIT.java @@ -115,6 +115,23 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio "api/versioning/versionhistories/" + versionHistory.getID())))); } + @Test + public void findOnePublicVersionHistoryWithVersioningDisabledTest() throws Exception { + configurationService.setProperty("versioning.enabled", false); + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(adminToken).perform(get("/api/versioning/versionhistories/" + Integer.MAX_VALUE)) + .andExpect(status().isForbidden()); + + getClient(epersonToken).perform(get("/api/versioning/versionhistories/" + Integer.MAX_VALUE)) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/versioning/versionhistories/" + Integer.MAX_VALUE)) + .andExpect(status().isUnauthorized()); + } + @Test public void findOnePrivateVersionHistoryByAdminTest() throws Exception { configurationService.setProperty("versioning.item.history.view.admin", true); @@ -328,6 +345,23 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.page.totalElements", is(3))); } + @Test + public void findVersionsOfVersionHistoryWithVersioningDisabledTest() throws Exception { + configurationService.setProperty("versioning.enabled", false); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) + .andExpect(status().isForbidden()); + + getClient(tokenEPerson).perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/versioning/versionhistories/" + versionHistory.getID() + "/versions")) + .andExpect(status().isUnauthorized()); + } + @Test public void findWorkspaceItemOfDraftVersionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -529,6 +563,23 @@ public class VersionHistoryRestRepositoryIT extends AbstractControllerIntegratio "Public test item", "2021-04-27", "ExtraEntry")))); } + @Test + public void findDraftVersionWithVersioningDisabledTest() throws Exception { + configurationService.setProperty("versioning.enabled", false); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/versioning/versionhistories/" + Integer.MAX_VALUE + "/draftVersion")) + .andExpect(status().isForbidden()); + + getClient(tokenEPerson).perform(get("/api/versioning/versionhistories/" + Integer.MAX_VALUE + "/draftVersion")) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/versioning/versionhistories/" + Integer.MAX_VALUE + "/draftVersion")) + .andExpect(status().isUnauthorized()); + } + @Test public void findVersionsOfVersionHistoryCheckPaginationAfterDeletingOfVersionTest() throws Exception { //disable file upload mandatory 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 94f5f988bc..c5e7f04c3b 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 @@ -228,6 +228,23 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { configurationService.setProperty("versioning.item.history.view.admin", false); } + @Test + public void findOneWithVersioningDisabledTest() throws Exception { + configurationService.setProperty("versioning.enabled", false); + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(adminToken).perform(get("/api/versioning/versions/" + Integer.MAX_VALUE)) + .andExpect(status().isForbidden()); + + getClient(epersonToken).perform(get("/api/versioning/versions/" + Integer.MAX_VALUE)) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/versioning/versions/" + Integer.MAX_VALUE)) + .andExpect(status().isUnauthorized()); + } + @Test public void versionForItemTest() throws Exception { @@ -813,6 +830,49 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { } } + @Test + public void createVersionWithVersioningDisabledTest() throws Exception { + configurationService.setProperty("versioning.enabled", false); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test") + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(adminToken).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().isForbidden()); + + getClient(epersonToken).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().isForbidden()); + + getClient().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().isUnauthorized()); + } + @Test public void patchReplaceSummaryTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -1294,6 +1354,52 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { ))); } + @Test + public void patchReplaceSummaryWithVersioningDisabledTest() throws Exception { + configurationService.setProperty("versioning.enabled", false); + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection test").build(); + + ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2021-04-27") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + String newSummary = "New Summary"; + List ops = new ArrayList(); + ReplaceOperation replaceOperation = new ReplaceOperation("/summary", newSummary); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + + String adminToken = getAuthToken(admin.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(adminToken).perform(patch("/api/versioning/versions/" + Integer.MAX_VALUE) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + getClient(epersonToken).perform(patch("/api/versioning/versions/" + Integer.MAX_VALUE) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + getClient().perform(patch("/api/versioning/versions/" + Integer.MAX_VALUE) + .content(patchBody) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnauthorized()); + } + @Test public void deleteVersionTest() throws Exception { context.turnOffAuthorisationSystem(); diff --git a/dspace/config/modules/versioning.cfg b/dspace/config/modules/versioning.cfg index 2c4b82dd85..55b104b8ba 100644 --- a/dspace/config/modules/versioning.cfg +++ b/dspace/config/modules/versioning.cfg @@ -3,6 +3,10 @@ #---------------------------------------------------# # These configs are used by the versioning system # #---------------------------------------------------# +# The property versioning.enabled is used to enabled/disable versioning in DSpace, +# the default value is true if it unset +# versioning.enabled= + # Control if the history overview of an item should only be shown to administrators # If enabled only the administrators for the item will be able to view the versioning history # If disabled anyone with READ permissions on the item will be able to view the versioning history From d88e4f4b6f6b966ce06f1982b8868ee231598257 Mon Sep 17 00:00:00 2001 From: Corrado Lombardi Date: Thu, 30 Sep 2021 12:44:44 +0200 Subject: [PATCH 0360/1254] [CST-4507] checkstyle fix --- .../rest/AuthorizationRestRepositoryIT.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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 b199a37c96..9a2ad4d817 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 @@ -1551,16 +1551,17 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration // verify that it works for administrators - with eperson parameter - Supplier baseFeatureRequest = () -> get("/api/authz/authorizations/search/objects") - .param("type", "core.community") - .param("uuid", comId) - .param("uuid", secondComId) - .param("projection", "level") - .param("embedLevelDepth", "1") - .param("feature", alwaysTrue.getName()) - .param("feature", alwaysFalse.getName()) - .param("feature", trueForLoggedUsers.getName()) - .param("feature", trueForAdmins.getName()); + Supplier baseFeatureRequest = () -> + get("/api/authz/authorizations/search/objects") + .param("type", "core.community") + .param("uuid", comId) + .param("uuid", secondComId) + .param("projection", "level") + .param("embedLevelDepth", "1") + .param("feature", alwaysTrue.getName()) + .param("feature", alwaysFalse.getName()) + .param("feature", trueForLoggedUsers.getName()) + .param("feature", trueForAdmins.getName()); getClient(adminToken).perform(baseFeatureRequest.get() .param("eperson", admin.getID().toString())) From aa9880f0f5868fa697fa3c32a5c96e63473bebc5 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 30 Sep 2021 08:39:36 -0400 Subject: [PATCH 0361/1254] [DS-3952] Adopt agreed-upon URL pattern for linking back from request email. --- .../dspace/app/rest/repository/RequestItemRepository.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 4f00513ff6..59daf3ec8c 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 @@ -270,7 +270,6 @@ public class RequestItemRepository return RequestItemRest.class; } - /** * Generate a link back to DSpace, to act on a request. * @@ -282,12 +281,10 @@ public class RequestItemRepository */ private String getLinkTokenEmail(String token) throws URISyntaxException, MalformedURLException { - String base = configurationService.getProperty("dspace.server.url"); + final String base = configurationService.getProperty("dspace.ui.url"); URI link = new URIBuilder(base) - .setPath("/api/" + RequestItemRest.CATEGORY - + '/' + RequestItemRest.PLURAL_NAME) - .addParameter("token", token) + .setPathSegments("request-a-copy", token) .build(); return link.toURL().toExternalForm(); From af20f0b8d8fc256160397ab56b5eee35d22f4f25 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 30 Sep 2021 13:42:54 -0400 Subject: [PATCH 0362/1254] [DS-3952] Test successful PUT more thoroughly, add tests for bad PUTs. --- dspace-server-webapp/pom.xml | 6 ++ .../app/rest/RequestItemRepositoryIT.java | 76 +++++++++++++++++-- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 686bd91edf..9b3c2c63ad 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -523,6 +523,12 @@ lucene-analyzers-stempel test + + org.exparity + hamcrest-date + 2.0.7 + test + 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 53ef84d9a2..b086624bac 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 @@ -9,11 +9,14 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.exparity.hamcrest.date.DateMatchers.within; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.text.IsEmptyString.emptyOrNullString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -29,12 +32,15 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.time.temporal.ChronoUnit; +import java.util.Date; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.Cookie; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.rest.converter.RequestItemConverter; @@ -236,9 +242,7 @@ public class RequestItemRepositoryIT hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) ))) .andDo((var result) -> requestTokenRef.set( - read(result.getResponse().getContentAsString(), "token"))) - .andReturn(); - + read(result.getResponse().getContentAsString(), "token"))); } finally { // Clean up the created request. RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); @@ -289,8 +293,7 @@ public class RequestItemRepositoryIT hasJsonPath("$._links.self.href", not(is(emptyOrNullString()))) ))) .andDo((var result) -> requestTokenRef.set( - read(result.getResponse().getContentAsString(), "token"))) - .andReturn(); + read(result.getResponse().getContentAsString(), "token"))); } finally { // Clean up the created request. RequestItemBuilder.deleteRequestItem(requestTokenRef.get()); @@ -307,7 +310,7 @@ public class RequestItemRepositoryIT @Test public void testCreateAndReturnBadRequest() throws SQLException, AuthorizeException, IOException, Exception { - System.out.println("createAndReturn (authenticated)"); + System.out.println("createAndReturn (bad requests)"); // Fake up a request in REST form. RequestItemRest rir = new RequestItemRest(); @@ -497,12 +500,69 @@ public class RequestItemRepositoryIT // Send the request. String authToken = getAuthToken(eperson.getEmail(), password); + AtomicReference requestTokenRef = new AtomicReference<>(); getClient(authToken).perform(put(URI_ROOT + '/' + itemRequest.getToken()) .contentType(contentType) .content(content)) .andExpect(status().isOk() - // .andMore? - ); + ) + .andDo((var result) -> requestTokenRef.set( + read(result.getResponse().getContentAsString(), "token"))); + RequestItem foundRequest + = requestItemService.findByToken(context, requestTokenRef.get()); + assertTrue("acceptRequest should be true", foundRequest.isAccept_request()); + assertThat("decision_date must be within a minute of now", + foundRequest.getDecision_date(), + within(1, ChronoUnit.MINUTES, new Date())); + } + + @Test + public void testPutBadRequest() + throws Exception { + System.out.println("put bad requests"); + + // Create an item request to approve. + RequestItem itemRequest = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .build(); + + String authToken = getAuthToken(eperson.getEmail(), password); + ObjectWriter mapperWriter = new ObjectMapper().writer(); + Map parameters; + String content; + + // 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); + getClient().perform(put(URI_ROOT + '/' + itemRequest.getToken()) + .contentType(contentType) + .content(content)) + .andExpect(status().isUnauthorized()); + + // Missing acceptRequest + parameters = Map.of( + "subject", "subject", + "responseMessage", "Request accepted"); + content = mapperWriter.writeValueAsString(parameters); + authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(put(URI_ROOT + '/' + itemRequest.getToken()) + .contentType(contentType) + .content(content)) + .andExpect(status().isUnprocessableEntity()); } /** From d1c31735f46d0d2bedee199101d81088f6e42637 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 30 Sep 2021 11:06:29 -0700 Subject: [PATCH 0363/1254] Fixed identifiers for range canvas references. --- .../dspace/app/rest/iiif/service/CanvasService.java | 11 +++++------ .../dspace/app/rest/iiif/service/ManifestService.java | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java index f4b1553159..eb4d192b88 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java @@ -99,13 +99,12 @@ public class CanvasService extends AbstractResourceService { /** * Ranges expect the Canvas object to have only an identifier. - * - * @param identifier the DSpace item identifier - * @param startCanvas the position of the canvas in list - * @return + * + * @param startCanvas the start canvas identifier + * @return canvas generator */ - protected CanvasGenerator getRangeCanvasReference(String identifier, String startCanvas) { - return new CanvasGenerator(IIIF_ENDPOINT + identifier + startCanvas); + protected CanvasGenerator getRangeCanvasReference(String startCanvas) { + return new CanvasGenerator(startCanvas); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java index 950fed25e3..95fa0cd044 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java @@ -181,11 +181,11 @@ public class ManifestService extends AbstractResourceService { } // add the bitstream canvas to the currRange currRange - .addCanvas(canvasService.getRangeCanvasReference(manifestId, canvasId.getIdentifier())); + .addCanvas(canvasService.getRangeCanvasReference(canvasId.getIdentifier())); lastRange = currRange; } } else { - lastRange.addCanvas(canvasService.getRangeCanvasReference(manifestId, canvasId.getIdentifier())); + lastRange.addCanvas(canvasService.getRangeCanvasReference(canvasId.getIdentifier())); } } } From 71cbf70865a4065c4cfb2537250008ac7836228d Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 30 Sep 2021 11:07:45 -0700 Subject: [PATCH 0364/1254] Excluding OtherContent bundle from bundles that can contain iiif image resources. --- .../java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java index 40d0a16e9d..0123784d92 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java @@ -115,7 +115,7 @@ public class IIIFUtils { private boolean isIIIFBundle(Bundle b) { return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", - "BRANDED_PREVIEW", "TEXT") + "BRANDED_PREVIEW", "TEXT", OTHER_CONTENT_BUNDLE) && b.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); From 5787be3dae519b7df20e667885f5e2b626061c39 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 30 Sep 2021 14:42:51 -0400 Subject: [PATCH 0365/1254] [DS-3952] Proper test for wrong authenticated user. --- .../org/dspace/app/rest/RequestItemRepositoryIT.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 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 b086624bac..042fe0096f 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 @@ -526,11 +526,12 @@ public class RequestItemRepositoryIT .createRequestItem(context, item, bitstream) .build(); - String authToken = getAuthToken(eperson.getEmail(), password); - ObjectWriter mapperWriter = new ObjectMapper().writer(); + String authToken; Map parameters; String content; + ObjectWriter mapperWriter = new ObjectMapper().writer(); + // Unauthenticated user parameters = Map.of( "acceptRequest", "true", @@ -548,10 +549,11 @@ public class RequestItemRepositoryIT "subject", "subject", "responseMessage", "Request accepted"); content = mapperWriter.writeValueAsString(parameters); - getClient().perform(put(URI_ROOT + '/' + itemRequest.getToken()) + authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(put(URI_ROOT + '/' + itemRequest.getToken()) .contentType(contentType) .content(content)) - .andExpect(status().isUnauthorized()); + .andExpect(status().isInternalServerError()); // Should be FORBIDDEN // Missing acceptRequest parameters = Map.of( From 3131c50e92a27bf6ff2fe6e6e7387f9f34530af6 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 30 Sep 2021 21:10:40 +0200 Subject: [PATCH 0366/1254] Reorder authz check to prioritize the cheaper ones --- .../authorize/AuthorizeServiceImpl.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 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 c2a785b3e7..e1f29d519c 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -250,13 +250,8 @@ public class AuthorizeServiceImpl implements AuthorizeService { if (e != null) { userToCheck = e; - // perform isAdmin check to see - // if user is an Admin on this object - DSpaceObject adminObject = useInheritance ? serviceFactory.getDSpaceObjectService(o) - .getAdminObject(c, o, action) : null; - - if (isAdmin(c, e, adminObject)) { - c.cacheAuthorizedAction(o, action, e, true, null); + // perform immediately isAdmin check as this is cheap + if (isAdmin(c, e)) { return true; } } @@ -280,13 +275,15 @@ public class AuthorizeServiceImpl implements AuthorizeService { ignoreCustomPolicies = !isAnyItemInstalled(c, Arrays.asList(((Bundle) o))); } if (o instanceof Item) { - if (workspaceItemService.findByItem(c, (Item) o) != null || - workflowItemService.findByItem(c, (Item) o) != null) { + // the isArchived check is fast and would exclude the possibility that the item + // is a workspace or workflow without further queries + if (!((Item) o).isArchived() && + (workspaceItemService.findByItem(c, (Item) o) != null || + workflowItemService.findByItem(c, (Item) o) != null)) { ignoreCustomPolicies = true; } } - for (ResourcePolicy rp : getPoliciesActionFilter(c, o, action)) { if (ignoreCustomPolicies @@ -322,6 +319,16 @@ public class AuthorizeServiceImpl implements AuthorizeService { } } + if (e != null) { + // if user is an Admin on this object + DSpaceObject adminObject = useInheritance ? serviceFactory.getDSpaceObjectService(o) + .getAdminObject(c, o, action) : null; + + if (isAdmin(c, e, adminObject)) { + c.cacheAuthorizedAction(o, action, e, true, null); + return true; + } + } // default authorization is denial c.cacheAuthorizedAction(o, action, e, false, null); return false; From 969a29f89e87d75ce71f2c2f49a03aeafcea5887 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 30 Sep 2021 21:13:12 +0200 Subject: [PATCH 0367/1254] Avoid to search current user by email --- .../app/rest/security/AdminRestPermissionEvaluatorPlugin.java | 3 +-- .../security/AuthorizeServicePermissionEvaluatorPlugin.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java index dd04fa95ee..0f3da93eac 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AdminRestPermissionEvaluatorPlugin.java @@ -53,8 +53,7 @@ public class AdminRestPermissionEvaluatorPlugin extends RestObjectPermissionEval EPerson ePerson = null; try { - ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal()); - + ePerson = context.getCurrentUser(); if (ePerson != null) { //Check if user is a repository admin diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java index 69cbcc3aa3..cc6c3f8528 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/AuthorizeServicePermissionEvaluatorPlugin.java @@ -76,7 +76,7 @@ public class AuthorizeServicePermissionEvaluatorPlugin extends RestObjectPermiss return false; } - ePerson = ePersonService.findByEmail(context, (String) authentication.getPrincipal()); + ePerson = context.getCurrentUser(); if (dSpaceObjectService != null && dsoId != null) { DSpaceObject dSpaceObject = dSpaceObjectService.find(context, dsoId); From c91c7caf1afa293c1f613ca6ce3b506734608366 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 30 Sep 2021 16:49:23 -0500 Subject: [PATCH 0368/1254] Update to Postgres JDBC driver 42.2.24 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e964a30c76..d5fb4e9c74 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.2.2.RELEASE 5.4.10.Final 6.0.18.Final - 42.2.23 + 42.2.24 8.8.1 1.2.22 From 64f900fd95c2ce0c933142a4cc2503365ec30103 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 1 Oct 2021 10:43:20 -0400 Subject: [PATCH 0369/1254] Implement Open Access request message to admin.s for Item Request. --- .../requestitem/RequestItemEmailNotifier.java | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 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 a655f6356e..4ad6595c80 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 @@ -5,11 +5,6 @@ * * http://www.dspace.org/license/ */ -/* - * Copyright 2021 Indiana University. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package org.dspace.app.requestitem; @@ -32,6 +27,7 @@ import org.dspace.core.Context; import org.dspace.core.Email; import org.dspace.core.I18nUtil; import org.dspace.core.LogHelper; +import org.dspace.eperson.EPerson; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; @@ -172,4 +168,60 @@ public class RequestItemEmailNotifier { LOG.info(LogHelper.getHeader(context, "sent_attach_requestItem", "token={}"), ri.getToken()); } + + /** + * Send, to a repository administrator, a request to open access to a + * requested object. + * + * @param context current DSpace session + * @param ri the item request that the approver is handling + * @throws IOException if the message body cannot be loaded or the message + * cannot be sent. + */ + static public void requestOpenAccess(Context context, RequestItem ri) + throws IOException { + Email message = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), + "request_item.author")); + + // Which Bitstream(s) requested? + Bitstream bitstream = ri.getBitstream(); + String bitstreamName; + if (bitstream != null) { + bitstreamName = bitstream.getName(); + } else { + bitstreamName = "all"; // TODO localize + } + + // Which Item? + Item item = ri.getItem(); + + // Fill the message's placeholders. + message.addArgument(bitstreamName); // {0} bitstream name or "all" + message.addArgument(item.getHandle()); // {1} Item handle + message.addArgument(ri.getToken()); // {2} Request token + message.addArgument(ri.getReqName()); // {3} Requester's name + message.addArgument(ri.getReqEmail()); // {4} Requester's address + + // Who gets this message? + String recipient; + EPerson submitter = item.getSubmitter(); + if (submitter != null) { + recipient = submitter.getEmail(); + } else { + recipient = configurationService.getProperty("mail.helpdesk"); + } + if (null == recipient) { + recipient = configurationService.getProperty("mail.admin"); + } + message.addRecipient(recipient); + + // Send the message. + try { + message.send(); + } catch (MessagingException ex) { + LOG.warn(LogHelper.getHeader(context, "error_mailing_requestItem", + ex.getMessage())); + throw new IOException("Open Access request not sent: " + ex.getMessage()); + } + } } From 8cbdc100df5e5fdc1cc8add121d22ecbff1b0ceb Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 1 Oct 2021 14:16:47 -0400 Subject: [PATCH 0370/1254] [DS-3952] Use correct template for OA request. --- .../org/dspace/app/requestitem/RequestItemEmailNotifier.java | 2 +- 1 file changed, 1 insertion(+), 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 4ad6595c80..dc4c50f96e 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 @@ -181,7 +181,7 @@ public class RequestItemEmailNotifier { static public void requestOpenAccess(Context context, RequestItem ri) throws IOException { Email message = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), - "request_item.author")); + "request_item.admin")); // Which Bitstream(s) requested? Bitstream bitstream = ri.getBitstream(); From 7e6326619d213abceda2eccbcd9fc5eb2f4ab4b4 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 1 Oct 2021 14:17:51 -0400 Subject: [PATCH 0371/1254] [DS-3952] Log Velocity errors. --- dspace-api/src/main/java/org/dspace/core/Email.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 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 2e9b058c06..c0d191caf5 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -48,6 +48,9 @@ import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.runtime.resource.loader.StringResourceLoader; import org.apache.velocity.runtime.resource.util.StringResourceRepository; import org.dspace.services.ConfigurationService; @@ -334,7 +337,13 @@ public class Email { } StringWriter writer = new StringWriter(); - template.merge(vctx, writer); + try { + template.merge(vctx, writer); + } catch (MethodInvocationException | ParseErrorException + | ResourceNotFoundException ex) { + LOG.error("Template not merged: {}", ex.getMessage()); + throw new MessagingException("Template not merged", ex); + } String fullMessage = writer.toString(); // Set some message header fields From 092cfc364c6c8ed0e0bcf65ce3ce4c1a845e50b8 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 1 Oct 2021 14:19:37 -0400 Subject: [PATCH 0372/1254] [DS-3952] Enable sending admin. request to open access to requested item. --- .../app/rest/repository/RequestItemRepository.java | 12 ++++++++++++ .../org/dspace/app/rest/RequestItemRepositoryIT.java | 3 ++- 2 files changed, 14 insertions(+), 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 e988f03c77..623d27885b 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 @@ -259,9 +259,21 @@ public class RequestItemRepository try { RequestItemEmailNotifier.sendResponse(context, ri, subject, message); } catch (IOException ex) { + LOG.warn("Response not sent: {}", ex.getMessage()); throw new RuntimeException("Response not sent", ex); } + // Perhaps send Open Access request to admin.s. + if (requestBody.findValue("suggestOpenAccess").asBoolean(false)) { + try { + RequestItemEmailNotifier.requestOpenAccess(context, ri); + } catch (IOException ex) { + LOG.warn("Open access request not sent: {}", ex.getMessage()); + throw new RuntimeException("Open access request not sent", ex); + } + } + + // Return updated request. RequestItemRest rir = requestItemConverter.convert(ri, Projection.DEFAULT); return rir; } 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 042fe0096f..dda51e9442 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 @@ -493,7 +493,8 @@ public class RequestItemRepositoryIT Map parameters = Map.of( "acceptRequest", "true", "subject", "subject", - "responseMessage", "Request accepted"); + "responseMessage", "Request accepted", + "suggestOpenAccess", "true"); String content = new ObjectMapper() .writer() .writeValueAsString(parameters); From efc9327cae8637f29403d1c4f0f1ebfe1c76a533 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 1 Oct 2021 15:03:59 -0700 Subject: [PATCH 0373/1254] Cache add. --- .../app/rest/iiif/IIIFEventConsumer.java | 125 ++++++++++++++++++ .../rest/iiif/service/CacheEvictService.java | 4 + .../service/util/CacheEvictBeanLocator.java | 4 + 3 files changed, 133 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFEventConsumer.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFEventConsumer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFEventConsumer.java new file mode 100644 index 0000000000..3ea4eaa864 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFEventConsumer.java @@ -0,0 +1,125 @@ +package org.dspace.app.rest.iiif; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.iiif.service.CacheEvictService; +import org.dspace.app.rest.iiif.service.util.CacheBeanLocator; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.event.Consumer; +import org.dspace.event.Event; + +public class IIIFEventConsumer implements Consumer { + + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFEventConsumer.class); + + // Gets the service bean. + CacheEvictService cacheEvictService = CacheBeanLocator.getCacheEvictService(); + + // When true all entries will be cleared from cache. + boolean clearAll = false; + + // Collects modified items for individual removal from cache. + private Set toEvictFromManifestCache = new HashSet<>(); + + + @Override + public void initialize() throws Exception { + + } + + @Override + public void consume(Context ctx, Event event) throws Exception { + int st = event.getSubjectType(); + if (!(st == Constants.BUNDLE || st == Constants.ITEM || st == Constants.BITSTREAM)) { + return; + } + DSpaceObject subject = event.getSubject(ctx); + int et = event.getEventType(); + + if (et == Event.DELETE || et == Event.REMOVE) { + log.warn("IIIF event consumer cannot remove a single item from the cache when " + + "a bundle is deleted. The entire manifests cache will be emptied."); + clearAll = true; + } + + if (st == Constants.BUNDLE) { + if ((et == Event.ADD || et == Event.MODIFY || et == Event.MODIFY_METADATA || et == Event.REMOVE || et == Event.DELETE) && subject != null) { + // set subject to be the parent Item. + subject = ((Bundle) subject).getItems().get(0); + if (log.isDebugEnabled()) { + log.debug("Transforming Bundle event into Item event for " + + subject.getID()); + } + } else { + return; + } + } + + if (st == Constants.BITSTREAM) { + if (et == Event.DELETE || et == Event.REMOVE) { + log.warn("IIIF event consumer cannot remove a single item from the cache when " + + "a bitstream is deleted. The entire manifests cache will be emptied."); + clearAll = true; + } + + if ((et == Event.ADD || et == Event.MODIFY_METADATA ) && subject != null) { + // set subject to be the parent Item. + Bundle bundle = ((Bitstream) subject).getBundles().get(0); + subject = bundle.getItems().get(0); + if (log.isDebugEnabled()) { + log.debug("Transforming Bitstream event into Item event for " + + subject.getID()); + } + } + else { + return; + } + } + + if (st == Constants.ITEM && et == Event.ADD) { + // nothing to evict from cache. + return; + } + + switch (et) { + case Event.ADD: + toEvictFromManifestCache.add(subject); + case Event.MODIFY: + toEvictFromManifestCache.add(subject); + case Event.MODIFY_METADATA: + toEvictFromManifestCache.add(subject); + case Event.REMOVE: + toEvictFromManifestCache.add(subject); + case Event.DELETE: + toEvictFromManifestCache.add(subject); + } + } + + @Override + public void end(Context ctx) throws Exception { + if (clearAll) { + cacheEvictService.evictAllCacheValues(); + } + else { + for (DSpaceObject dso : toEvictFromManifestCache) { + UUID uuid = dso.getID(); + cacheEvictService.evictSingleCacheValue(uuid.toString()); + } + } + clearAll = false; + toEvictFromManifestCache.clear(); + } + + @Override + public void finish(Context ctx) throws Exception { + + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java new file mode 100644 index 0000000000..8a0f3955ca --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java @@ -0,0 +1,4 @@ +package org.dspace.app.rest.iiif.service; + +public class CacheEvictService { +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java new file mode 100644 index 0000000000..b89af2a2d9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java @@ -0,0 +1,4 @@ +package org.dspace.app.rest.iiif.service.util; + +public class CacheEvictBeanLocator { +} From 26456121aacc5198b67bda3c0eef2456304bc371 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 1 Oct 2021 15:33:25 -0700 Subject: [PATCH 0374/1254] Completed consumer used to manage iiif cache. --- ...sumer.java => IIIFCacheEventConsumer.java} | 19 ++++++++-- .../rest/iiif/service/CacheEvictService.java | 28 ++++++++++++++ .../service/util/CacheEvictBeanLocator.java | 38 ++++++++++++++++++- dspace/config/dspace.cfg | 5 +++ 4 files changed, 85 insertions(+), 5 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/{IIIFEventConsumer.java => IIIFCacheEventConsumer.java} (87%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFEventConsumer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java similarity index 87% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFEventConsumer.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java index 3ea4eaa864..0248f41dd1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFEventConsumer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.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.iiif; import java.util.HashSet; @@ -6,7 +13,7 @@ import java.util.UUID; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.iiif.service.CacheEvictService; -import org.dspace.app.rest.iiif.service.util.CacheBeanLocator; +import org.dspace.app.rest.iiif.service.util.CacheEvictBeanLocator; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; @@ -15,12 +22,16 @@ import org.dspace.core.Context; import org.dspace.event.Consumer; import org.dspace.event.Event; -public class IIIFEventConsumer implements Consumer { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFEventConsumer.class); +/** + * This consumer is used to evict modified items from the manifests cache. + */ +public class IIIFCacheEventConsumer implements Consumer { + + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFCacheEventConsumer.class); // Gets the service bean. - CacheEvictService cacheEvictService = CacheBeanLocator.getCacheEvictService(); + CacheEvictService cacheEvictService = CacheEvictBeanLocator.getCacheEvictService(); // When true all entries will be cleared from cache. boolean clearAll = false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java index 8a0f3955ca..b26948b091 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java @@ -1,4 +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.iiif.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; + +@Service public class CacheEvictService { + + // The cache that is managed by this service. + static final String CACHE_NAME = "manifests"; + + @Autowired + CacheManager cacheManager; + + public void evictSingleCacheValue(String cacheKey) { + cacheManager.getCache(CACHE_NAME).evict(cacheKey); + } + + public void evictAllCacheValues() { + cacheManager.getCache(CACHE_NAME).clear(); + } + } + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java index b89af2a2d9..f9e13018ee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java @@ -1,4 +1,40 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ package org.dspace.app.rest.iiif.service.util; -public class CacheEvictBeanLocator { +import org.dspace.app.rest.iiif.service.CacheEvictService; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * Exposes the Spring managed cache evict service to the event consumer. + */ +@Component +public class CacheEvictBeanLocator implements ApplicationContextAware { + + private static ApplicationContext context; + + private static final String CACHE_SERVICE = "cacheEvictService"; + + @Override + public void setApplicationContext(ApplicationContext appContext) + throws BeansException { + context = appContext; + + } + + public static ApplicationContext getApplicationContext() { + return context; + } + + public static CacheEvictService getCacheEvictService(){ + return (CacheEvictService) context.getBean(CACHE_SERVICE);} + } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index cedc6549bf..787a24fca8 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -696,6 +696,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. +# Add iiif here, if you are using international image interoperability framework support. event.dispatcher.default.consumers = versioning, discovery, eperson # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) @@ -730,6 +731,10 @@ event.consumer.versioning.filters = Item+Install event.consumer.authority.class = org.dspace.authority.indexer.AuthorityConsumer event.consumer.authority.filters = Item+Modify|Modify_Metadata +# iiif consumer +event.consumer.iiif.class = org.dspace.app.rest.iiif.IIIFCacheEventConsumer +event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+Remove:Bundle+ALL:Bitstream+All + # ...set to true to enable testConsumer messages to standard output #testConsumer.verbose = true From 67012de266b0db9bf784a2cf1008c4522e10e1b1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 1 Oct 2021 15:47:50 -0700 Subject: [PATCH 0375/1254] Added comment to dspace.cfg. --- dspace/config/dspace.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 787a24fca8..36b6c4fda8 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -696,11 +696,11 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. -# Add iiif here, if you are using international image interoperability framework support. +# Add iiif here, if you are using iiif support. Currently the iiif consumer must be removed when using some CLI tools. event.dispatcher.default.consumers = versioning, discovery, eperson # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) -event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher +event.dispatcher.noindex.class = org.dspace.event.BasicDispatcherq event.dispatcher.noindex.consumers = eperson # consumer to maintain the discovery index From 6385df9779b8be8b5381e9605c51a362c4339542 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 2 Oct 2021 12:33:47 +0200 Subject: [PATCH 0376/1254] minor fix --- dspace/config/modules/versioning.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/modules/versioning.cfg b/dspace/config/modules/versioning.cfg index 55b104b8ba..c69d63e57c 100644 --- a/dspace/config/modules/versioning.cfg +++ b/dspace/config/modules/versioning.cfg @@ -5,7 +5,7 @@ #---------------------------------------------------# # The property versioning.enabled is used to enabled/disable versioning in DSpace, # the default value is true if it unset -# versioning.enabled= +# versioning.enabled = true # Control if the history overview of an item should only be shown to administrators # If enabled only the administrators for the item will be able to view the versioning history @@ -23,4 +23,4 @@ versioning.item.history.include.submitter=false # The property versioning.block.entity is used to disable versioning # for items with EntityType, the default value is true if it unset. -# versioning.block.entity= \ No newline at end of file +# versioning.block.entity = true \ No newline at end of file From 7905e8cc2544a650a95bce2c01b463c3ebf88b82 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 2 Oct 2021 13:27:52 -0700 Subject: [PATCH 0377/1254] Updated consumer. --- .../app/rest/iiif/IIIFCacheEventConsumer.java | 22 +++++++++++++------ .../service/util/CacheEvictBeanLocator.java | 5 +++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java index 0248f41dd1..5077f96b52 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java @@ -56,12 +56,13 @@ public class IIIFCacheEventConsumer implements Consumer { if (et == Event.DELETE || et == Event.REMOVE) { log.warn("IIIF event consumer cannot remove a single item from the cache when " + - "a bundle is deleted. The entire manifests cache will be emptied."); + "a bundle is deleted. The entire cache will be cleared."); clearAll = true; } if (st == Constants.BUNDLE) { - if ((et == Event.ADD || et == Event.MODIFY || et == Event.MODIFY_METADATA || et == Event.REMOVE || et == Event.DELETE) && subject != null) { + if ((et == Event.ADD || et == Event.MODIFY || et == Event.MODIFY_METADATA || et == Event.REMOVE + || et == Event.DELETE) && subject != null) { // set subject to be the parent Item. subject = ((Bundle) subject).getItems().get(0); if (log.isDebugEnabled()) { @@ -76,7 +77,7 @@ public class IIIFCacheEventConsumer implements Consumer { if (st == Constants.BITSTREAM) { if (et == Event.DELETE || et == Event.REMOVE) { log.warn("IIIF event consumer cannot remove a single item from the cache when " + - "a bitstream is deleted. The entire manifests cache will be emptied."); + "a bitstream is deleted. The entire cache will be cleared."); clearAll = true; } @@ -88,8 +89,7 @@ public class IIIFCacheEventConsumer implements Consumer { log.debug("Transforming Bitstream event into Item event for " + subject.getID()); } - } - else { + } else { return; } } @@ -102,14 +102,23 @@ public class IIIFCacheEventConsumer implements Consumer { switch (et) { case Event.ADD: toEvictFromManifestCache.add(subject); + break; case Event.MODIFY: toEvictFromManifestCache.add(subject); + break; case Event.MODIFY_METADATA: toEvictFromManifestCache.add(subject); + break; case Event.REMOVE: toEvictFromManifestCache.add(subject); + break; case Event.DELETE: toEvictFromManifestCache.add(subject); + break; + default: { + log.warn("IIIFCacheEventConsumer should not have been given this kind of " + + "subject in an event, skipping: " + event.toString()); + } } } @@ -117,8 +126,7 @@ public class IIIFCacheEventConsumer implements Consumer { public void end(Context ctx) throws Exception { if (clearAll) { cacheEvictService.evictAllCacheValues(); - } - else { + } else { for (DSpaceObject dso : toEvictFromManifestCache) { UUID uuid = dso.getID(); cacheEvictService.evictSingleCacheValue(uuid.toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java index f9e13018ee..0e2b5ac9a2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java @@ -34,7 +34,8 @@ public class CacheEvictBeanLocator implements ApplicationContextAware { return context; } - public static CacheEvictService getCacheEvictService(){ - return (CacheEvictService) context.getBean(CACHE_SERVICE);} + public static CacheEvictService getCacheEvictService() { + return (CacheEvictService) context.getBean(CACHE_SERVICE); + } } From 597cf4efb56823902811cf0de610b6743bbf2980 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Sat, 2 Oct 2021 13:28:16 -0700 Subject: [PATCH 0378/1254] Configuration update. --- dspace/config/dspace.cfg | 2 +- dspace/config/modules/solr-statistics.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 36b6c4fda8..899c4886cb 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -700,7 +700,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher event.dispatcher.default.consumers = versioning, discovery, eperson # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) -event.dispatcher.noindex.class = org.dspace.event.BasicDispatcherq +event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher event.dispatcher.noindex.consumers = eperson # consumer to maintain the discovery index diff --git a/dspace/config/modules/solr-statistics.cfg b/dspace/config/modules/solr-statistics.cfg index ab0e9b86ba..5b67cd4799 100644 --- a/dspace/config/modules/solr-statistics.cfg +++ b/dspace/config/modules/solr-statistics.cfg @@ -13,7 +13,7 @@ solr-statistics.server = ${solr.server}/statistics # A comma-separated list that contains the bundles for which the bitstreams will be displayed -solr-statistics.query.filter.bundles=ORIGINAL, IIIF +solr-statistics.query.filter.bundles=ORIGINAL # Name of the "configset" (bundle of template core files) which will be used to # create new Solr cores when sharding the statistics data. From 93bdf9add6ce23129dc7c770e115a7b67b97ba37 Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Mon, 4 Oct 2021 14:03:29 +1100 Subject: [PATCH 0379/1254] Removed relationships flag --- .../org/dspace/app/itemimport/service/ItemImportService.java | 5 ----- 1 file changed, 5 deletions(-) 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 ab4473ad08..af333764b5 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 @@ -229,11 +229,6 @@ public interface ItemImportService { */ public void setUseWorkflowSendEmail(boolean useWorkflowSendMail); - /** - * @param processRelationships whether to look for a relationships manifest - */ - public void setProcessRelationships(boolean processRelationships); - /** * Set quiet flag * From c01f4c1ca016152af8c90891189fbb7056b84e0c Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Mon, 4 Oct 2021 14:04:24 +1100 Subject: [PATCH 0380/1254] Fix broken command-line collection arg --- .../app/itemimport/ItemImportCLITool.java | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java index 7cad97df31..3cfed8503a 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLITool.java @@ -337,29 +337,36 @@ public class ItemImportCLITool { // validate each collection arg to see if it's a real collection for (int i = 0; i < collections.length; i++) { - // is the ID a handle? - if (collections[i].indexOf('/') != -1) { - // string has a / so it must be a handle - try and resolve - // it - mycollections.add((Collection) handleService - .resolveToObject(c, collections[i])); - // resolved, now make sure it's a collection - if ((mycollections.get(i) == null) - || (mycollections.get(i).getType() != Constants.COLLECTION)) { - mycollections.set(i, null); + Collection resolved = null; + + if (collections[i] != null) { + + // is the ID a handle? + if (collections[i].indexOf('/') != -1) { + // string has a / so it must be a handle - try and resolve + // it + resolved = ((Collection) handleService + .resolveToObject(c, collections[i])); + + } else { + // not a handle, try and treat it as an integer collection database ID + resolved = collectionService.find(c, UUID.fromString(collections[i])); + } - } else if (collections[i] != null) { - // not a handle, try and treat it as an integer collection database ID - mycollections.set(i, collectionService.find(c, UUID.fromString(collections[i]))); + } // was the collection valid? - if (mycollections.get(i) == null) { + if ((resolved == null) + || (resolved.getType() != Constants.COLLECTION)) { throw new IllegalArgumentException("Cannot resolve " + collections[i] + " to collection"); } + // add resolved collection to list + mycollections.add(resolved); + // print progress info String owningPrefix = ""; @@ -368,7 +375,7 @@ public class ItemImportCLITool { } System.out.println(owningPrefix + " Collection: " - + mycollections.get(i).getName()); + + resolved.getName()); } } // end of validating collections From 00740cc0214c027aeec907bd6a1644beae28f576 Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Mon, 4 Oct 2021 14:40:29 +1100 Subject: [PATCH 0381/1254] don't attempt to create relationships in test mode --- .../org/dspace/app/itemimport/ItemImportServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 880a59a3ee..1ad5e003c0 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 @@ -346,6 +346,12 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea for (String itemIdentifier : identifierList) { + if (isTest) { + System.out.println("\tAdding relationship (type: " + relationshipType + + ") from " + folderName + " to " + itemIdentifier); + continue; + } + //find referenced item Item relationItem = resolveRelatedItem(c, itemIdentifier); if (null == relationItem) { From 8bdd1138eb69d0e37993a18152dc2379b769c73c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 4 Oct 2021 16:24:42 +0200 Subject: [PATCH 0382/1254] implemented flyway script to delete deprecated columns --- ...ollection_table_drop_workflow_stem_columns.sql | 15 +++++++++++++++ ...ollection_table_drop_workflow_stem_columns.sql | 15 +++++++++++++++ ...ollection_table_drop_workflow_stem_columns.sql | 15 +++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql new file mode 100644 index 0000000000..ae8f1e7ef5 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql @@ -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/ +-- + +------------------------------------------------------------------------------------- +---- ALTER table collection +------------------------------------------------------------------------------------- + +ALTER TABLE collection DROP COLUMN workflow_step_1; +ALTER TABLE collection DROP COLUMN workflow_step_2; +ALTER TABLE collection DROP COLUMN workflow_step_3; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql new file mode 100644 index 0000000000..ae8f1e7ef5 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql @@ -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/ +-- + +------------------------------------------------------------------------------------- +---- ALTER table collection +------------------------------------------------------------------------------------- + +ALTER TABLE collection DROP COLUMN workflow_step_1; +ALTER TABLE collection DROP COLUMN workflow_step_2; +ALTER TABLE collection DROP COLUMN workflow_step_3; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql new file mode 100644 index 0000000000..ae8f1e7ef5 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.10.04__alter_collection_table_drop_workflow_stem_columns.sql @@ -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/ +-- + +------------------------------------------------------------------------------------- +---- ALTER table collection +------------------------------------------------------------------------------------- + +ALTER TABLE collection DROP COLUMN workflow_step_1; +ALTER TABLE collection DROP COLUMN workflow_step_2; +ALTER TABLE collection DROP COLUMN workflow_step_3; \ No newline at end of file From 11bea56988c53a0f046a809771dd2ca364f00e8c Mon Sep 17 00:00:00 2001 From: Tyson Lloyd Thwaites Date: Tue, 5 Oct 2021 12:15:37 +1100 Subject: [PATCH 0383/1254] fixed checkstyle violation --- .../java/org/dspace/app/itemimport/ItemImportServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 1ad5e003c0..255d1d5785 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 @@ -347,7 +347,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea for (String itemIdentifier : identifierList) { if (isTest) { - System.out.println("\tAdding relationship (type: " + relationshipType + + System.out.println("\tAdding relationship (type: " + relationshipType + ") from " + folderName + " to " + itemIdentifier); continue; } @@ -397,7 +397,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem); Relationship persistedRelationship = relationshipService.create( c, leftItem, rightItem, foundRelationshipType, leftPlace, rightPlace); - relationshipService.update(c, persistedRelationship); + // relationshipService.update(c, persistedRelationship); System.out.println("\tAdded relationship (type: " + relationshipType + ") from " + leftItem.getHandle() + " to " + rightItem.getHandle()); From c516f9c856710ce814d6add792d2cfebdeee11c2 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 6 Oct 2021 17:08:53 +0200 Subject: [PATCH 0384/1254] [CST-4659] hint fixed for dc.type --- dspace/config/submission-forms.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 878b79ffd6..0ae058b9b8 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -161,8 +161,7 @@ true dropdown - Select the type(s) of content of the item. To select more than one value in the list, you may - have to hold down the "CTRL" or "Shift" key. + Select the type of content of the item. From 1dc4f629f234cb2d56101aa4257ec27f4f892b4b Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 6 Oct 2021 11:31:21 -0400 Subject: [PATCH 0385/1254] [DS-3952] Identify OA request with approver, not requester. Document OA request template. --- .../app/requestitem/RequestItemEmailNotifier.java | 11 ++++++----- dspace/config/emails/request_item.admin | 10 ++++++++++ 2 files changed, 16 insertions(+), 5 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 dc4c50f96e..d72e42eac1 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 @@ -196,11 +196,12 @@ public class RequestItemEmailNotifier { Item item = ri.getItem(); // Fill the message's placeholders. - message.addArgument(bitstreamName); // {0} bitstream name or "all" - message.addArgument(item.getHandle()); // {1} Item handle - message.addArgument(ri.getToken()); // {2} Request token - message.addArgument(ri.getReqName()); // {3} Requester's name - message.addArgument(ri.getReqEmail()); // {4} Requester's address + EPerson approver = context.getCurrentUser(); + 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 // Who gets this message? String recipient; diff --git a/dspace/config/emails/request_item.admin b/dspace/config/emails/request_item.admin index 849079574b..c0443c60f8 100644 --- a/dspace/config/emails/request_item.admin +++ b/dspace/config/emails/request_item.admin @@ -1,3 +1,13 @@ +## E-mail sent when someone approving an item request, requests that the Item +## or Bitstream be made public. +## +## Parameters: {0} the Handle of the requested bitstream, or "all" +## {1} the Handle of the item +## {2} the unique token which identifies the item request +## {3} the name of the approver +## {4} the approver's email address +## See org.dspace.core.Email for information on the format of this file. +## #set($subject = 'Request for Open Access') ${params[3]}, with address ${params[4]}, From 65dc9cbb14f709214ab0961c0c9c3ada15fec854 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 6 Oct 2021 17:45:07 +0200 Subject: [PATCH 0386/1254] converted RelationshipTypeRestController in link repository --- .../content/RelationshipTypeServiceImpl.java | 5 + .../content/dao/RelationshipTypeDAO.java | 12 +++ .../dao/impl/RelationshipTypeDAOImpl.java | 18 ++++ .../service/RelationshipTypeService.java | 10 ++ .../rest/RelationshipTypeRestController.java | 94 ------------------- .../relation/EntityTypeHalLinkFactory.java | 43 --------- .../dspace/app/rest/model/EntityTypeRest.java | 9 ++ .../EntityTypeRelationshipLinkRepository.java | 73 ++++++++++++++ 8 files changed, 127 insertions(+), 137 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java index 29472436bd..9e5de89ae2 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java @@ -174,4 +174,9 @@ public class RelationshipTypeServiceImpl implements RelationshipTypeService { } relationshipTypeDAO.delete(context, relationshipType); } + + @Override + public int countByEntityType(Context context, EntityType entityType) throws SQLException { + return relationshipTypeDAO.countByEntityType(context, entityType); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java index e451e48cf2..f374f2cabc 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java @@ -120,4 +120,16 @@ public interface RelationshipTypeDAO extends GenericDAO { List findByEntityType(Context context, EntityType entityType, Boolean isLeft, Integer limit, Integer offset) throws SQLException; + + /** + * Count all RelationshipType objects for which the given EntityType + * is equal to either the leftType or the rightType + * + * @param context DSpace context object + * @param entityType The EntityType object used to check the leftType and rightType properties + * @return Total RelationshipType objects + * @throws SQLException If database error + */ + public int countByEntityType(Context context, EntityType entityType) throws SQLException; + } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java index 2192634793..7fff2a1f57 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java @@ -8,6 +8,7 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; +import java.util.LinkedList; import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -92,6 +93,9 @@ public class RelationshipTypeDAOImpl extends AbstractHibernateDAO orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(relationshipTypeRoot.get(RelationshipType_.ID))); + criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, RelationshipType.class, limit, offset); } @@ -120,4 +124,18 @@ public class RelationshipTypeDAOImpl extends AbstractHibernateDAO relationshipTypeRoot = criteriaQuery.from(RelationshipType.class); + criteriaQuery.select(relationshipTypeRoot); + criteriaQuery.where(criteriaBuilder.or( + criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.leftType), entityType), + criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.rightType), entityType) + )); + return count(context, criteriaQuery, criteriaBuilder, relationshipTypeRoot); + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java index 94ebbca41e..1dd3feaac8 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java @@ -103,6 +103,16 @@ public interface RelationshipTypeService extends DSpaceCRUDService findByEntityType(Context context, EntityType entityType, Integer limit, Integer offset) throws SQLException; + /** + * Count all RelationshipType objects for which the given EntityType + * is equal to either the leftType or the rightType + * + * @param context DSpace context object + * @param entityType The EntityType object used to check the leftType and rightType properties + * @return Total RelationshipType objects + * @throws SQLException If database error + */ + public int countByEntityType(Context context, EntityType entityType) throws SQLException; /** * This method will return a list of RelationshipType objects for which the given EntityType object is equal diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java deleted file mode 100644 index 46aefbe69e..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.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; - -import java.sql.SQLException; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.link.HalLinkService; -import org.dspace.app.rest.model.RelationshipTypeRest; -import org.dspace.app.rest.model.hateoas.RelationshipTypeResource; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.app.rest.utils.Utils; -import org.dspace.content.EntityType; -import org.dspace.content.RelationshipType; -import org.dspace.content.service.EntityTypeService; -import org.dspace.content.service.RelationshipTypeService; -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.web.PagedResourcesAssembler; -import org.springframework.hateoas.PagedModel; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -/** - * This controller will handle all the incoming calls on the api/core/entitytypes/{id}/relationshiptypes endpoint - * where the id parameter can be filled in to match a specific entityType and then get all the relationshipTypes - * for the given EntityType - */ -@RestController -@RequestMapping("/api/core/entitytypes/{id}/relationshiptypes") -public class RelationshipTypeRestController { - - - @Autowired - private RelationshipTypeService relationshipTypeService; - - @Autowired - private EntityTypeService entityTypeService; - - @Autowired - private ConverterService converter; - - @Autowired - private Utils utils; - - @Autowired - private HalLinkService halLinkService; - - /** - * This method will retrieve all the RelationshipTypes that conform to the given EntityType by the given ID and - * it will return this in a wrapped resource. - * - * @param id The ID of the EntityType objects that we'll use to retrieve the RelationshipTypes - * @param response The response object - * @param request The request object - * @param pageable The pagination object - * @param assembler The assembler object - * @return The wrapped resource containing the list of RelationshipType objects as defined above - * @throws SQLException If something goes wrong - */ - @RequestMapping(method = RequestMethod.GET) - public PagedModel retrieve(@PathVariable Integer id, - HttpServletResponse response, - HttpServletRequest request, - Pageable pageable, - PagedResourcesAssembler assembler) throws SQLException { - Context context = ContextUtil.obtainContext(request); - EntityType entityType = entityTypeService.find(context, id); - List list = relationshipTypeService.findByEntityType(context, entityType, -1, -1); - - Page relationshipTypeRestPage = converter - .toRestPage(list, pageable, utils.obtainProjection()); - - Page relationshipTypeResources = relationshipTypeRestPage - .map(relationshipTypeRest -> new RelationshipTypeResource(relationshipTypeRest, utils)); - relationshipTypeResources.forEach(halLinkService::addLinks); - PagedModel result = assembler.toModel(relationshipTypeResources); - return result; - - - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java deleted file mode 100644 index d2741181c1..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.link.relation; - -import java.util.LinkedList; - -import org.dspace.app.rest.RelationshipTypeRestController; -import org.dspace.app.rest.link.HalLinkFactory; -import org.dspace.app.rest.model.hateoas.EntityTypeResource; -import org.springframework.data.domain.Pageable; -import org.springframework.hateoas.Link; -import org.springframework.stereotype.Component; - -/** - * This class' purpose is to add the links to the EntityTypeResource. This function and class will be called - * and used - * when the HalLinkService addLinks methods is called as it'll iterate over all the different factories and check - * whether - * these are allowed to create links for said resource or not. - */ -@Component -public class EntityTypeHalLinkFactory extends HalLinkFactory { - @Override - protected void addLinks(EntityTypeResource halResource, Pageable pageable, LinkedList list) throws Exception { - list.add(buildLink("relationshiptypes", getMethodOn().retrieve( - halResource.getContent().getId(), null, null, null, null))); - } - - @Override - protected Class getControllerClass() { - return RelationshipTypeRestController.class; - } - - @Override - protected Class getResourceClass() { - return EntityTypeResource.class; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java index 454e5a82b6..89a0d21eb8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java @@ -14,11 +14,20 @@ import org.dspace.app.rest.RestResourceController; * for the EntityTypeResource class. * Refer to {@link org.dspace.content.EntityType} for explanation of the properties */ +@LinksRest(links = { + @LinkRest( + name = EntityTypeRest.RELATION_SHIP_TYPES, + method = "getEntityTypeRelationship" + ) +}) public class EntityTypeRest extends BaseObjectRest { + private static final long serialVersionUID = 8166078961459192770L; + public static final String NAME = "entitytype"; public static final String NAME_PLURAL = "entitytypes"; public static final String CATEGORY = "core"; + public static final String RELATION_SHIP_TYPES = "relationshipTypes"; public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java new file mode 100644 index 0000000000..7d70c37282 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.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.repository; +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.EntityTypeRest; +import org.dspace.app.rest.model.RelationshipTypeRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.EntityType; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.RelationshipTypeService; +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.stereotype.Component; + +/** + * Link repository for "relationships" subresource of an individual EntityType + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.NAME + "." + EntityTypeRest.RELATION_SHIP_TYPES) +public class EntityTypeRelationshipLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private EntityTypeService entityTypeService; + @Autowired + private RelationshipTypeService relationshipTypeService; + + /** + * This method will retrieve all the RelationshipTypes that conform + * to the given EntityType by the given ID and it will return this in a wrapped resource. + * + * @param request The request object + * @param id The ID of the EntityType objects that we'll use to retrieve the RelationshipTypes + * @param pageable The pagination object + * @param projection + * @return List of RelationshipType objects as defined above + */ + public Page getEntityTypeRelationship(@Nullable HttpServletRequest request, + Integer id, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Pageable pageable = utils.getPageable(optionalPageable); + EntityType entityType = entityTypeService.find(context, id); + if (Objects.isNull(entityType)) { + throw new ResourceNotFoundException("No such EntityType: " + id); + } + int total = relationshipTypeService.countByEntityType(context, entityType); + List list = relationshipTypeService.findByEntityType(context, entityType, + pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); + return converter.toRestPage(list, pageable, total, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file From 0dfee17944df93931eb04ef6e3681458fc2fc62a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 6 Oct 2021 17:45:43 +0200 Subject: [PATCH 0387/1254] added tests --- .../app/rest/EntityTypeRestRepositoryIT.java | 35 ++++++ .../RelationshipTypeRestControllerIT.java | 116 +++++++++++++++++- .../rest/matcher/RelationshipTypeMatcher.java | 9 +- .../test/AbstractEntityIntegrationTest.java | 3 + 4 files changed, 152 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 0f6060c011..9ad063f127 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -15,8 +15,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.matcher.EntityTypeMatcher; +import org.dspace.app.rest.matcher.RelationshipTypeMatcher; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.content.EntityType; +import org.dspace.content.RelationshipType; import org.dspace.content.service.EntityTypeService; import org.hamcrest.Matchers; import org.junit.Test; @@ -168,4 +170,37 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$.page.number", is(1))); } + @Test + public void findEntityTypeWithEmbedRelatioshipTypeTest() throws Exception { + + EntityType person = entityTypeService.findByEntityType(context, "Person"); + EntityType orgunit = entityTypeService.findByEntityType(context, "OrgUnit"); + EntityType project = entityTypeService.findByEntityType(context, "Project"); + EntityType publication = entityTypeService.findByEntityType(context, "Publication"); + EntityType journalIssue = entityTypeService.findByEntityType(context, "journalIssue"); + + RelationshipType relationshipType1 = relationshipTypeService.findbyTypesAndTypeName(context, + publication, person, "isAuthorOfPublication", "isPublicationOfAuthor"); + RelationshipType relationshipType2 = relationshipTypeService.findbyTypesAndTypeName(context, + publication, project, "isProjectOfPublication", "isPublicationOfProject"); + RelationshipType relationshipType3 = relationshipTypeService.findbyTypesAndTypeName(context, + publication, orgunit, "isOrgUnitOfPublication", "isPublicationOfOrgUnit"); + RelationshipType relationshipType4 = relationshipTypeService.findbyTypesAndTypeName(context, + journalIssue, publication, "isPublicationOfJournalIssue", "isJournalIssueOfPublication"); + RelationshipType relationshipType5 = relationshipTypeService.findbyTypesAndTypeName(context, + publication, orgunit, "isAuthorOfPublication","isPublicationOfAuthor"); + + getClient().perform(get("/api/core/entitytypes/" + publication.getID()) + .param("embed", "relationshipTypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", EntityTypeMatcher.matchEntityTypeEntry(publication))) + .andExpect(jsonPath("$._embedded.relationshipTypes._embedded.relationshipTypes", containsInAnyOrder( + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType1), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType2), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType3), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType4), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType5) + ))); + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java index eb7945023d..0c0ae1153a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java @@ -6,7 +6,7 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest; - +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -20,6 +20,7 @@ import org.dspace.app.rest.matcher.RelationshipTypeMatcher; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EntityTypeBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.content.Collection; @@ -86,8 +87,7 @@ public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationT RelationshipType relationshipType5 = relationshipTypeService .findbyTypesAndTypeName(context, publicationEntityType, orgunitEntityType, "isAuthorOfPublication", "isPublicationOfAuthor"); - getClient().perform(get("/api/core/entitytypes/" + publicationEntityType.getID() + "/relationshiptypes") - .param("projection", "full")) + getClient().perform(get("/api/core/entitytypes/" + publicationEntityType.getID() + "/relationshiptypes")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType1), @@ -95,7 +95,115 @@ public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationT RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType3), RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType4), RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType5) - ))); + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(5))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findAllRelationshipTypesEmptyResponseTest() throws Exception { + context.turnOffAuthorisationSystem(); + EntityType testEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "TestEntityType").build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/entitytypes/" + testEntityType.getID() + "/relationshiptypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationshiptypes").isEmpty()) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findAllRelationshipTypesForPublicationsPaginationTest() throws Exception { + + EntityType person = entityTypeService.findByEntityType(context, "Person"); + EntityType orgunit = entityTypeService.findByEntityType(context, "OrgUnit"); + EntityType project = entityTypeService.findByEntityType(context, "Project"); + EntityType publication = entityTypeService.findByEntityType(context, "Publication"); + EntityType journalIssue = entityTypeService.findByEntityType(context, "journalIssue"); + + RelationshipType relationshipType1 = relationshipTypeService.findbyTypesAndTypeName(context, + publication, person, "isAuthorOfPublication", "isPublicationOfAuthor"); + RelationshipType relationshipType2 = relationshipTypeService.findbyTypesAndTypeName(context, + publication, project, "isProjectOfPublication", "isPublicationOfProject"); + RelationshipType relationshipType3 = relationshipTypeService.findbyTypesAndTypeName(context, + publication, orgunit, "isOrgUnitOfPublication", "isPublicationOfOrgUnit"); + RelationshipType relationshipType4 = relationshipTypeService.findbyTypesAndTypeName(context, + journalIssue, publication, "isPublicationOfJournalIssue", "isJournalIssueOfPublication"); + RelationshipType relationshipType5 = relationshipTypeService.findbyTypesAndTypeName(context, + publication, orgunit, "isAuthorOfPublication","isPublicationOfAuthor"); + + getClient().perform(get("/api/core/entitytypes/" + publication.getID() + "/relationshiptypes") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType1), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType2) + ))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(5))) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient().perform(get("/api/core/entitytypes/" + publication.getID() + "/relationshiptypes") + .param("size", "2") + .param("page", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType3), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType5) + ))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(5))) + .andExpect(jsonPath("$.page.number", is(1))); + + getClient().perform(get("/api/core/entitytypes/" + publication.getID() + "/relationshiptypes") + .param("size", "2") + .param("page", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationshiptypes", contains( + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType4) + ))) + .andExpect(jsonPath("$.page.size", is(2))) + .andExpect(jsonPath("$.page.totalElements", is(5))) + .andExpect(jsonPath("$.page.number", is(2))); + } + + @Test + public void findAllRelationshipTypesForPublicationsEmbedTest() throws Exception { + + EntityType publicationEntityType = entityTypeService.findByEntityType(context, "Publication"); + EntityType personEntityType = entityTypeService.findByEntityType(context, "Person"); + EntityType projectEntityType = entityTypeService.findByEntityType(context, "Project"); + EntityType orgunitEntityType = entityTypeService.findByEntityType(context, "OrgUnit"); + EntityType journalIssueEntityType = entityTypeService.findByEntityType(context, "journalIssue"); + + RelationshipType relationshipType1 = relationshipTypeService + .findbyTypesAndTypeName(context, publicationEntityType, personEntityType, "isAuthorOfPublication", + "isPublicationOfAuthor"); + RelationshipType relationshipType2 = relationshipTypeService + .findbyTypesAndTypeName(context, publicationEntityType, projectEntityType, "isProjectOfPublication", + "isPublicationOfProject"); + RelationshipType relationshipType3 = relationshipTypeService + .findbyTypesAndTypeName(context, publicationEntityType, orgunitEntityType, "isOrgUnitOfPublication", + "isPublicationOfOrgUnit"); + RelationshipType relationshipType4 = relationshipTypeService + .findbyTypesAndTypeName(context, journalIssueEntityType, publicationEntityType, + "isPublicationOfJournalIssue", "isJournalIssueOfPublication"); + RelationshipType relationshipType5 = relationshipTypeService + .findbyTypesAndTypeName(context, publicationEntityType, orgunitEntityType, "isAuthorOfPublication", + "isPublicationOfAuthor"); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/relationships?embed=relationshipType")) + .andExpect(status().isOk()); + } + + @Test + public void findAllRelationshipTypesNotFoundTest() throws Exception { + getClient().perform(get("/api/core/entitytypes/" + Integer.MAX_VALUE + "/relationshiptypes")) + .andExpect(status().isNotFound()); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java index 4d46fe9f2e..53c755011e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java @@ -15,7 +15,6 @@ import static org.hamcrest.Matchers.is; import org.dspace.content.EntityType; import org.dspace.content.RelationshipType; import org.hamcrest.Matcher; -import org.hamcrest.Matchers; public class RelationshipTypeMatcher { @@ -81,12 +80,8 @@ public class RelationshipTypeMatcher { hasJsonPath("$.rightMaxCardinality", is(rightMaxCardinality)), hasJsonPath("$.type", is("relationshiptype")), hasJsonPath("$._links.self.href", containsString("/api/core/relationshiptypes/" + id)), - hasJsonPath("$._embedded.leftType", Matchers.allOf( - EntityTypeMatcher.matchEntityTypeExplicitValuesEntry(leftEntityTypeId, leftEntityTypeLabel) - )), - hasJsonPath("$._embedded.rightType", Matchers.is( - EntityTypeMatcher.matchEntityTypeExplicitValuesEntry(rightEntityTypeId, rightEntityTypeLabel) - )) + hasJsonPath("$._links.leftType.href", containsString("/api/core/entitytypes/" + leftEntityTypeId)), + hasJsonPath("$._links.rightType.href", containsString("/api/core/entitytypes/" + rightEntityTypeId)) ); } } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java index ab3c1acc05..7319b453ac 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java @@ -11,6 +11,7 @@ import org.dspace.builder.EntityTypeBuilder; import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.content.EntityType; import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.RelationshipTypeService; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +19,8 @@ public class AbstractEntityIntegrationTest extends AbstractControllerIntegration @Autowired private EntityTypeService entityTypeService; + @Autowired + protected RelationshipTypeService relationshipTypeService; /** * This method will call the setUp method from AbstractControllerIntegrationTest. From 47712ecde35dd91bd0b70e667552e7246bc32dc8 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 6 Oct 2021 18:36:37 +0200 Subject: [PATCH 0388/1254] minor fix --- .../main/java/org/dspace/app/rest/model/EntityTypeRest.java | 2 +- .../java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java index 89a0d21eb8..f777b3a29c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java @@ -27,7 +27,7 @@ public class EntityTypeRest extends BaseObjectRest { public static final String NAME = "entitytype"; public static final String NAME_PLURAL = "entitytypes"; public static final String CATEGORY = "core"; - public static final String RELATION_SHIP_TYPES = "relationshipTypes"; + public static final String RELATION_SHIP_TYPES = "relationshiptypes"; public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 9ad063f127..da2be08fd4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -191,10 +191,10 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { publication, orgunit, "isAuthorOfPublication","isPublicationOfAuthor"); getClient().perform(get("/api/core/entitytypes/" + publication.getID()) - .param("embed", "relationshipTypes")) + .param("embed", "relationshiptypes")) .andExpect(status().isOk()) .andExpect(jsonPath("$", EntityTypeMatcher.matchEntityTypeEntry(publication))) - .andExpect(jsonPath("$._embedded.relationshipTypes._embedded.relationshipTypes", containsInAnyOrder( + .andExpect(jsonPath("$._embedded.relationshiptypes._embedded.relationshiptypes", containsInAnyOrder( RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType1), RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType2), RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType3), From 55beed65ba843f877ccfa5350d7a19de511321b4 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 6 Oct 2021 13:18:57 -0400 Subject: [PATCH 0389/1254] [DS-3952] Don't create a RequestItem until #build() is called. --- .../dspace/builder/RequestItemBuilder.java | 88 +++++++++++++------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index 3ffc3b5af8..a9e4aed174 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -9,6 +9,7 @@ package org.dspace.builder; import java.sql.SQLException; +import java.util.Date; import javax.validation.constraints.NotNull; import org.apache.logging.log4j.LogManager; @@ -34,28 +35,15 @@ public class RequestItemBuilder public static final String REQ_PATH = "test/file"; private RequestItem requestItem; + private Item item; + private Bitstream bitstream; + private Date decisionDate; + private boolean accepted; protected RequestItemBuilder(Context context) { super(context); } - @Override - public void cleanup() - throws Exception { - LOG.debug("cleanup()"); - try ( Context ctx = new Context(); ) { - ctx.turnOffAuthorisationSystem(); - requestItem = ctx.reloadEntity(requestItem); - if (null != requestItem) { - delete(ctx, requestItem); - ctx.complete(); - requestItem = null; - } else { - LOG.debug("nothing to clean up."); - } - } - } - /** * Initialize a RequestItem. * @@ -71,6 +59,42 @@ public class RequestItemBuilder } private RequestItemBuilder create(Item item, Bitstream bitstream) { + this.item = item; + this.bitstream = bitstream; + return this; + } + + /** + * Set the date on which a decision was made concerning this request. + * + * @param date the date of the decision. + * @return this builder. + */ + public RequestItemBuilder withDecisionDate(Date date) { + this.decisionDate = date; + return this; + } + + /** + * Set whether request has been accepted. Does not set the decision + * date. + * + * @param accepted true if request is accepted. + * @return this builder. + */ + public RequestItemBuilder withAcceptRequest(boolean accepted) { + this.accepted = accepted; + return this; + } + + @Override + public RequestItem build() { + LOG.atDebug() + .withLocation() + .log("Building request with item ID {} and bitstream ID {}", + () -> item.getID().toString(), + () -> bitstream.getID().toString()); + String token; try { token = requestItemService.createRequest(context, bitstream, item, @@ -80,21 +104,29 @@ public class RequestItemBuilder return handleException(ex); } requestItem = requestItemService.findByToken(context, token); - requestItem.setAccept_request(false); + requestItem.setAccept_request(accepted); + requestItem.setDecision_date(decisionDate); + requestItemService.update(context, requestItem); - return this; + + return requestItem; } @Override - public RequestItem build() { - LOG.atDebug() - .withLocation() - .log("Building request with item ID {} and bitstream ID {}", - () -> requestItem.getItem().getID().toString(), - () -> requestItem.getBitstream().getID().toString()); - - // Nothing to build. - return requestItem; + public void cleanup() + throws Exception { + LOG.debug("cleanup()"); + try ( Context ctx = new Context(); ) { + ctx.turnOffAuthorisationSystem(); + requestItem = ctx.reloadEntity(requestItem); + if (null != requestItem) { + delete(ctx, requestItem); + ctx.complete(); + requestItem = null; + } else { + LOG.debug("nothing to clean up."); + } + } } @Override From db72b43f0fcf058774e14d07ba0adb9885fe8e52 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 6 Oct 2021 13:19:52 -0400 Subject: [PATCH 0390/1254] [DS-3952] Don't allow accept/reject of a completed request. --- .../repository/RequestItemRepository.java | 8 ++++++ .../app/rest/RequestItemRepositoryIT.java | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+) 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 623d27885b..d013566a2c 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 @@ -240,6 +240,14 @@ public class RequestItemRepository throw new AuthorizeException("Not authorized to approve this request"); } + // Do not permit updates after a decision has been given. + Date decisionDate = ri.getDecision_date(); + if (null != decisionDate) { + throw new UnprocessableEntityException("Request was " + + (ri.isAccept_request() ? "granted" : "denied") + + " on " + decisionDate + " and may not be updated."); + } + // Make the changes JsonNode acceptRequestNode = requestBody.findValue("acceptRequest"); if (null == acceptRequestNode) { 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 dda51e9442..d7eb707bbb 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 @@ -568,6 +568,32 @@ public class RequestItemRepositoryIT .andExpect(status().isUnprocessableEntity()); } + @Test + public void testPutCompletedRequest() + throws Exception { + System.out.println("put completed request"); + + // Create an item request that is already denied. + RequestItem itemRequest = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .withAcceptRequest(false) + .withDecisionDate(new Date()) + .build(); + + // Try to accept it again. + Map parameters = Map.of( + "acceptRequest", "true", + "subject", "subject", + "responseMessage", "Request accepted"); + ObjectWriter mapperWriter = new ObjectMapper().writer(); + String content = mapperWriter.writeValueAsString(parameters); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(put(URI_ROOT + '/' + itemRequest.getToken()) + .contentType(contentType) + .content(content)) + .andExpect(status().isUnprocessableEntity()); + } + /** * Test of getDomainClass method, of class RequestItemRepository. */ From cde623c3449992031c87161f6a42c1907035bb4e Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 6 Oct 2021 12:48:22 -0700 Subject: [PATCH 0391/1254] Moved iiif event consumer to dspace-api. --- .../dspace/iiif}/CacheEvictBeanLocator.java | 13 ++++++------- .../org/dspace/iiif}/CacheEvictService.java | 8 ++++---- .../dspace}/iiif/IIIFCacheEventConsumer.java | 18 +++++++++--------- .../java/org/dspace/app/rest/Application.java | 12 ++++++++++++ .../org/dspace/app/rest/utils/ContextUtil.java | 2 +- dspace/config/dspace.cfg | 3 +-- 6 files changed, 33 insertions(+), 23 deletions(-) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-api/src/main/java/org/dspace/iiif}/CacheEvictBeanLocator.java (74%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service => dspace-api/src/main/java/org/dspace/iiif}/CacheEvictService.java (88%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-api/src/main/java/org/dspace}/iiif/IIIFCacheEventConsumer.java (91%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java similarity index 74% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java rename to dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java index 0e2b5ac9a2..e171b1b813 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/CacheEvictBeanLocator.java +++ b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java @@ -1,3 +1,4 @@ +package org.dspace.iiif; /** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source @@ -5,18 +6,14 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; -import org.dspace.app.rest.iiif.service.CacheEvictService; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.stereotype.Component; /** - * Exposes the Spring managed cache evict service to the event consumer. + * Exposes the Spring web application's IIIF cache evict service to the DSpace event consumer. */ -@Component public class CacheEvictBeanLocator implements ApplicationContextAware { private static ApplicationContext context; @@ -27,7 +24,6 @@ public class CacheEvictBeanLocator implements ApplicationContextAware { public void setApplicationContext(ApplicationContext appContext) throws BeansException { context = appContext; - } public static ApplicationContext getApplicationContext() { @@ -35,7 +31,10 @@ public class CacheEvictBeanLocator implements ApplicationContextAware { } public static CacheEvictService getCacheEvictService() { - return (CacheEvictService) context.getBean(CACHE_SERVICE); + if (context != null) { + return (CacheEvictService) context.getBean(CACHE_SERVICE); + } + return null; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java similarity index 88% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java rename to dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java index b26948b091..edb24b41ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CacheEvictService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java @@ -5,13 +5,14 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.iiif; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; -import org.springframework.stereotype.Service; -@Service +/** + * Removes items from the iiif manifests cache. + */ public class CacheEvictService { // The cache that is managed by this service. @@ -29,4 +30,3 @@ public class CacheEvictService { } } - diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java similarity index 91% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java rename to dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java index 5077f96b52..7441395842 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java @@ -5,15 +5,13 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif; +package org.dspace.iiif; import java.util.HashSet; import java.util.Set; import java.util.UUID; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.iiif.service.CacheEvictService; -import org.dspace.app.rest.iiif.service.util.CacheEvictBeanLocator; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; @@ -124,12 +122,14 @@ public class IIIFCacheEventConsumer implements Consumer { @Override public void end(Context ctx) throws Exception { - if (clearAll) { - cacheEvictService.evictAllCacheValues(); - } else { - for (DSpaceObject dso : toEvictFromManifestCache) { - UUID uuid = dso.getID(); - cacheEvictService.evictSingleCacheValue(uuid.toString()); + if (cacheEvictService != null) { + if (clearAll) { + cacheEvictService.evictAllCacheValues(); + } else { + for (DSpaceObject dso : toEvictFromManifestCache) { + UUID uuid = dso.getID(); + cacheEvictService.evictSingleCacheValue(uuid.toString()); + } } } clearAll = false; 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 4ee6570483..61709e0a6b 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 @@ -21,6 +21,8 @@ import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; import org.dspace.app.sitemap.GenerateSitemaps; import org.dspace.app.util.DSpaceContextListener; +import org.dspace.iiif.CacheEvictBeanLocator; +import org.dspace.iiif.CacheEvictService; import org.dspace.utils.servlet.DSpaceWebappServletFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -147,6 +149,16 @@ public class Application extends SpringBootServletInitializer { return new DSpaceLinkRelationProvider(); } + @Bean + protected CacheEvictBeanLocator cacheEvictBeanLocator() { + return new CacheEvictBeanLocator(); + } + + @Bean + protected CacheEvictService cacheEvictService() { + return new CacheEvictService(); + } + @Bean public WebMvcConfigurer webMvcConfigurer() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java index 93ef8bb302..73813c01b5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ContextUtil.java @@ -208,4 +208,4 @@ public class ContextUtil { context.abort(); } } -} \ No newline at end of file +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 899c4886cb..9f68e10578 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -696,7 +696,6 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. -# Add iiif here, if you are using iiif support. Currently the iiif consumer must be removed when using some CLI tools. event.dispatcher.default.consumers = versioning, discovery, eperson # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) @@ -732,7 +731,7 @@ event.consumer.authority.class = org.dspace.authority.indexer.AuthorityConsumer event.consumer.authority.filters = Item+Modify|Modify_Metadata # iiif consumer -event.consumer.iiif.class = org.dspace.app.rest.iiif.IIIFCacheEventConsumer +event.consumer.iiif.class = org.dspace.iiif.IIIFCacheEventConsumer event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+Remove:Bundle+ALL:Bitstream+All # ...set to true to enable testConsumer messages to standard output From bc893656217c912bd9fb30315950d6e4c062464a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 7 Oct 2021 19:09:20 +0200 Subject: [PATCH 0392/1254] Implement community feedbacks --- .../dspace/content/EntityTypeServiceImpl.java | 10 +++ .../content/WorkspaceItemServiceImpl.java | 27 ++++++-- .../content/service/CollectionService.java | 11 +-- .../content/service/EntityTypeService.java | 9 +++ .../main/java/org/dspace/core/Constants.java | 2 +- .../CollectionIndexFactoryImpl.java | 2 +- .../rdbms/EntityTypeServiceInitializer.java | 68 +++++++++++++++++++ ...type_from_item_template_to_collection.sql} | 5 -- ...type_from_item_template_to_collection.sql} | 5 -- ...type_from_item_template_to_collection.sql} | 5 -- .../dspace/app/bulkedit/MetadataImportIT.java | 22 +++++- .../org/dspace/app/rest/csv/CsvExportIT.java | 3 +- 12 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/storage/rdbms/EntityTypeServiceInitializer.java rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/{V7.0_2021.09.24__Insert_unset_entity_type_to_dspace.sql => V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql} (88%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/{V7.0_2021.09.24__Insert_unset_entity_type_to_dspace.sql => V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql} (88%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/{V7.0_2021.09.24__Insert_unset_entity_type_to_dspace.sql => V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql} (88%) diff --git a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java index 2790c36924..0e0c6d51e5 100644 --- a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java @@ -26,6 +26,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.EntityTypeDAO; import org.dspace.content.service.EntityTypeService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.SolrSearchCore; import org.dspace.discovery.indexobject.IndexableCollection; @@ -168,4 +169,13 @@ public class EntityTypeServiceImpl implements EntityTypeService { return entityTypeDAO.countEntityTypesByNames(context, names); } + @Override + public void initDefaultEntityTypeNames(Context context) throws SQLException, AuthorizeException { + EntityType noneEntityType = this.findByEntityType(context, Constants.ENTITY_TYPE_NONE); + if (Objects.isNull(noneEntityType)) { + noneEntityType = this.create(context, Constants.ENTITY_TYPE_NONE); + this.update(context, noneEntityType); + } + } + } 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 6b1124b6f4..2f49c8fcc2 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.Util; @@ -109,14 +110,18 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { .addPolicy(context, item, Constants.DELETE, item.getSubmitter(), ResourcePolicy.TYPE_SUBMISSION); - Optional optionalType = collection.getMetadata() - .stream() - .filter(x -> x.getMetadataField().toString('.') - .equalsIgnoreCase("dspace.entity.type")) - .findFirst(); + Optional colEntityType = getDSpaceEntityType(collection); + Optional itemEntityType = getDSpaceEntityType(item); - if (optionalType.isPresent()) { - MetadataValue original = optionalType.get(); + if (colEntityType.isPresent() && itemEntityType.isPresent() && + !StringUtils.equals(colEntityType.get().getValue(), itemEntityType.get().getValue())) { + throw new IllegalStateException("It is not possible to deposite item with entity type : " + + itemEntityType.get().getValue() + " into the collection with different type : " + + colEntityType.get().getValue()); + } + + if (colEntityType.isPresent() && itemEntityType.isEmpty()) { + MetadataValue original = colEntityType.get(); MetadataField metadataField = original.getMetadataField(); MetadataSchema metadataSchema = metadataField.getMetadataSchema(); itemService.addMetadata(context, item, metadataSchema.getName(), metadataField.getElement(), @@ -152,6 +157,14 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { return workspaceItem; } + private Optional getDSpaceEntityType(DSpaceObject dSpaceObject) { + return dSpaceObject.getMetadata() + .stream() + .filter(x -> x.getMetadataField().toString('.') + .equalsIgnoreCase("dspace.entity.type")) + .findFirst(); + } + @Override public WorkspaceItem create(Context c, WorkflowItem workflowItem) throws SQLException, AuthorizeException { WorkspaceItem workspaceItem = workspaceItemDAO.create(c, new WorkspaceItem()); 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 5c64ed532b..54d542c863 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 @@ -348,7 +348,7 @@ public interface CollectionService * Returns Collections for which the current user has 'submit' privileges. * NOTE: for better performance, this method retrieves its results from an * index (cache) and does not query the database directly. - * This means that results may be stale or outdated until DS-4524 is resolved" + * This means that results may be stale or outdated until https://github.com/DSpace/DSpace/issues/2853 is resolved" * * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement @@ -369,7 +369,8 @@ public interface CollectionService * Returns Collections for which the current user has 'submit' privileges. * NOTE: for better performance, this method retrieves its results from an * index (cache) and does not query the database directly. - * This means that results may be stale or outdated until DS-4524 is resolved" + * This means that results may be stale or outdated until + * https://github.com/DSpace/DSpace/issues/2853 is resolved" * * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement @@ -389,7 +390,8 @@ public interface CollectionService * Counts the number of Collection for which the current user has 'submit' privileges. * NOTE: for better performance, this method retrieves its results from an index (cache) * and does not query the database directly. - * This means that results may be stale or outdated until DS-4524 is resolved." + * This means that results may be stale or outdated until + * https://github.com/DSpace/DSpace/issues/2853 is resolved." * * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement @@ -407,7 +409,8 @@ public interface CollectionService * Counts the number of Collection for which the current user has 'submit' privileges. * NOTE: for better performance, this method retrieves its results from an index (cache) * and does not query the database directly. - * This means that results may be stale or outdated until DS-4524 is resolved." + * This means that results may be stale or outdated until + * https://github.com/DSpace/DSpace/issues/2853 is resolved." * * @param q limit the returned collection to those with metadata values matching the query terms. * The terms are used to make also a prefix query on SOLR so it can be used to implement diff --git a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java index 5dd9c357ae..d0a1a498ce 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java @@ -91,4 +91,13 @@ public interface EntityTypeService extends DSpaceCRUDService { */ public int countEntityTypesByNames(Context context, List names) throws SQLException; + /** + * Initializes the EntityType names, and marks them "permanent". + * + * @param context DSpace context object + * @throws SQLException Database exception + * @throws AuthorizeException Authorization error + */ + public void initDefaultEntityTypeNames(Context context) throws SQLException, AuthorizeException; + } diff --git a/dspace-api/src/main/java/org/dspace/core/Constants.java b/dspace-api/src/main/java/org/dspace/core/Constants.java index 4fae3af642..f730ef6545 100644 --- a/dspace-api/src/main/java/org/dspace/core/Constants.java +++ b/dspace-api/src/main/java/org/dspace/core/Constants.java @@ -230,7 +230,7 @@ public class Constants { /* * Label used by the special entity type assigned when no explicit assignment is defined */ - public static final String UNSET_ENTITY_TYPE = "unset"; + public static final String ENTITY_TYPE_NONE = "none"; /** * Default constructor diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java index 57c1ffc4e5..c2bacfe502 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/CollectionIndexFactoryImpl.java @@ -130,7 +130,7 @@ public class CollectionIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl Date: Thu, 7 Oct 2021 10:49:45 -0700 Subject: [PATCH 0393/1254] Updated event consumer. --- .../java/org/dspace/iiif/CacheEvictBeanLocator.java | 3 +-- .../java/org/dspace/iiif/IIIFCacheEventConsumer.java | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java index e171b1b813..58e24885ca 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java +++ b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java @@ -1,4 +1,3 @@ -package org.dspace.iiif; /** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source @@ -6,7 +5,7 @@ package org.dspace.iiif; * * http://www.dspace.org/license/ */ - +package org.dspace.iiif; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java index 7441395842..84537243b0 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java @@ -26,16 +26,16 @@ import org.dspace.event.Event; */ public class IIIFCacheEventConsumer implements Consumer { - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFCacheEventConsumer.class); + private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFCacheEventConsumer.class); // Gets the service bean. - CacheEvictService cacheEvictService = CacheEvictBeanLocator.getCacheEvictService(); + private final CacheEvictService cacheEvictService = CacheEvictBeanLocator.getCacheEvictService(); // When true all entries will be cleared from cache. - boolean clearAll = false; + private boolean clearAll = false; // Collects modified items for individual removal from cache. - private Set toEvictFromManifestCache = new HashSet<>(); + private final Set toEvictFromManifestCache = new HashSet<>(); @Override @@ -45,6 +45,9 @@ public class IIIFCacheEventConsumer implements Consumer { @Override public void consume(Context ctx, Event event) throws Exception { + if (cacheEvictService == null) { + return; + } int st = event.getSubjectType(); if (!(st == Constants.BUNDLE || st == Constants.ITEM || st == Constants.BITSTREAM)) { return; From f92592846debeff3b6757a7f1b60ba0a898a49aa Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 7 Oct 2021 11:52:10 -0700 Subject: [PATCH 0394/1254] Added "org.dspace.iiif" to component scan in ApplicationConfig. --- .../src/main/java/org/dspace/app/rest/Application.java | 10 ---------- .../org/dspace/app/rest/utils/ApplicationConfig.java | 2 +- 2 files changed, 1 insertion(+), 11 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 61709e0a6b..517b2c0f16 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 @@ -149,16 +149,6 @@ public class Application extends SpringBootServletInitializer { return new DSpaceLinkRelationProvider(); } - @Bean - protected CacheEvictBeanLocator cacheEvictBeanLocator() { - return new CacheEvictBeanLocator(); - } - - @Bean - protected CacheEvictService cacheEvictService() { - return new CacheEvictService(); - } - @Bean public WebMvcConfigurer webMvcConfigurer() { 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 8df6caf9b2..f180124d15 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 @@ -25,7 +25,7 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; @Configuration @EnableSpringDataWebSupport @ComponentScan( {"org.dspace.app.rest.converter", "org.dspace.app.rest.repository", "org.dspace.app.rest.utils", - "org.dspace.app.configuration"}) + "org.dspace.app.configuration", "org.dspace.iiif"}) public class ApplicationConfig { // Allowed CORS origins ("Access-Control-Allow-Origin" header) // Can be overridden in DSpace configuration From f15a18254ea20a11b0362a8902f74eeafb17b5bd Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 7 Oct 2021 11:54:03 -0700 Subject: [PATCH 0395/1254] Added component annotations. Added comment. Fixed import. --- .../src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java | 3 +++ .../src/main/java/org/dspace/iiif/CacheEvictService.java | 2 ++ .../src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java | 1 + .../src/main/java/org/dspace/app/rest/Application.java | 2 -- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java index 58e24885ca..b82242e6e1 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java +++ b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java @@ -6,13 +6,16 @@ * http://www.dspace.org/license/ */ package org.dspace.iiif; + import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; /** * Exposes the Spring web application's IIIF cache evict service to the DSpace event consumer. */ +@Component public class CacheEvictBeanLocator implements ApplicationContextAware { private static ApplicationContext context; diff --git a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java index edb24b41ef..e1b9676039 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java @@ -9,10 +9,12 @@ package org.dspace.iiif; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; /** * Removes items from the iiif manifests cache. */ +@Component public class CacheEvictService { // The cache that is managed by this service. diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java index 84537243b0..4346b11093 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java @@ -45,6 +45,7 @@ public class IIIFCacheEventConsumer implements Consumer { @Override public void consume(Context ctx, Event event) throws Exception { + // The service null when the web application context is not provided (i.e. command line operations). if (cacheEvictService == null) { return; } 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 517b2c0f16..4ee6570483 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 @@ -21,8 +21,6 @@ import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; import org.dspace.app.sitemap.GenerateSitemaps; import org.dspace.app.util.DSpaceContextListener; -import org.dspace.iiif.CacheEvictBeanLocator; -import org.dspace.iiif.CacheEvictService; import org.dspace.utils.servlet.DSpaceWebappServletFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 5ecca8d90560f1f50676661255b9a3192a989fec Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 7 Oct 2021 12:14:16 -0700 Subject: [PATCH 0396/1254] Added iiif to default consumers. --- dspace/config/dspace.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 9f68e10578..1207c9ecff 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -696,7 +696,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. -event.dispatcher.default.consumers = versioning, discovery, eperson +event.dispatcher.default.consumers = versioning, discovery, eperson, iiif # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher From f0b60cf8ce899c9e5592fe4f3e8a15e508c7e3a9 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 8 Oct 2021 10:13:26 +0200 Subject: [PATCH 0397/1254] fix failed test --- .../src/test/java/org/dspace/app/packager/PackagerIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index ddc2b115d8..bf392dccec 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -72,13 +72,13 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) .withName("Sub Community") .build(); - col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 2") + .withEntityType("Publication").build(); // Create a new Publication (which is an Article) article = ItemBuilder.createItem(context, col1) .withTitle("Article") .withIssueDate("2017-10-17") - .withEntityType("Publication") .build(); tempFile = File.createTempFile("packagerExportTest", ".zip"); From df7231e6b6e4f4a59f6deb27fd5af011811f7565 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 8 Oct 2021 11:45:10 +0200 Subject: [PATCH 0398/1254] restore the original tests --- .../app/rest/EntityTypeRestRepositoryIT.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 364ce828ca..9025855139 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -24,6 +24,7 @@ import org.dspace.builder.EntityTypeBuilder; import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.service.EntityTypeService; +import org.dspace.core.Constants; import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.external.service.ExternalDataService; import org.hamcrest.Matchers; @@ -50,12 +51,11 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { //We expect a 200 OK status .andExpect(status().isOk()) //The type has to be 'discover' - .andExpect(jsonPath("$.page.totalElements", is(8))) + .andExpect(jsonPath("$.page.totalElements", is(7))) //There needs to be a self link to this endpoint .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "unset")), EntityTypeMatcher .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), @@ -77,7 +77,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()) //The type has to be 'discover' .andExpect(jsonPath("$.page.size", is(5))) - .andExpect(jsonPath("$.page.totalElements", is(8))) + .andExpect(jsonPath("$.page.totalElements", is(7))) .andExpect(jsonPath("$.page.totalPages", is(2))) //There needs to be a self link to this endpoint .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) @@ -88,7 +88,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "unset")) + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")) ))); getClient().perform(get("/api/core/entitytypes").param("size", "5").param("page", "1")) @@ -97,14 +97,13 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()) //The type has to be 'discover' .andExpect(jsonPath("$.page.size", is(5))) - .andExpect(jsonPath("$.page.totalElements", is(8))) + .andExpect(jsonPath("$.page.totalElements", is(7))) .andExpect(jsonPath("$.page.totalPages", is(2))) .andExpect(jsonPath("$.page.number", is(1))) //There needs to be a self link to this endpoint .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), EntityTypeMatcher .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalVolume")), EntityTypeMatcher @@ -135,7 +134,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "unset")) + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/entitytypes?"), @@ -150,7 +149,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { Matchers.containsString("/api/core/entitytypes?"), Matchers.containsString("page=2"), Matchers.containsString("size=3")))) .andExpect(jsonPath("$.page.size", is(3))) - .andExpect(jsonPath("$.page.totalElements", is(8))) + .andExpect(jsonPath("$.page.totalElements", is(7))) .andExpect(jsonPath("$.page.totalPages", is(3))) .andExpect(jsonPath("$.page.number", is(0))); @@ -161,7 +160,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")) + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalVolume")) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/entitytypes?"), @@ -179,7 +178,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { Matchers.containsString("/api/core/entitytypes?"), Matchers.containsString("page=2"), Matchers.containsString("size=3")))) .andExpect(jsonPath("$.page.size", is(3))) - .andExpect(jsonPath("$.page.totalElements", is(8))) + .andExpect(jsonPath("$.page.totalElements", is(7))) .andExpect(jsonPath("$.page.totalPages", is(3))) .andExpect(jsonPath("$.page.number", is(1))); } From 724ea77ef78cef2491289b492d63e1448bcc3ca7 Mon Sep 17 00:00:00 2001 From: Joost Date: Fri, 8 Oct 2021 12:28:30 +0200 Subject: [PATCH 0399/1254] implementing feedback --- dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java | 1 - .../src/main/java/org/dspace/app/harvest/HarvestCli.java | 5 ----- .../org/dspace/app/harvest/HarvestScriptConfiguration.java | 5 +++-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java index 5c02d34756..f2630572e3 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/Harvest.java @@ -198,7 +198,6 @@ public class Harvest extends DSpaceRunnable { purgeCollection(context, collection); context.complete(); - //TODO: implement this... remove all items and remember to unset "last-harvested" settings } else if ("reimport".equals(command)) { // Delete all items in a collection. Useful for testing fresh harvests. if (collection == null || context.getCurrentUser() == null) { diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCli.java b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCli.java index 2ea16e9295..8c9766e934 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCli.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestCli.java @@ -13,11 +13,6 @@ import org.apache.commons.cli.ParseException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -/** - * Test class for harvested collections. - * - * @author Alexey Maslov - */ public class HarvestCli extends Harvest { /** diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java index 5830cef520..982973e47c 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java @@ -48,8 +48,9 @@ public class HarvestScriptConfiguration extends ScriptConfigu options.addOption("s", "setup", false, "Set the collection up for harvesting"); options.addOption("S", "start", false, "start the harvest loop"); options.addOption("R", "reset", false, "reset harvest status on all collections"); - options.addOption("P", "purge", false, "purge all harvestable collections"); - options.addOption("o", "reimport", false, "reimport all items in the collection"); + options.addOption("P", "purgeCollections", false, "purge all harvestable collections"); + options.addOption("o", "reimport", false, "reimport all items in the collection, " + + "this is equivalent to -p -r, purging all items in a collection and reimporting them"); options.addOption("c", "collection", true, "harvesting collection (handle or id)"); options.addOption("t", "type", true, From 62917409a40e6f198684b665c859f5e8a95028b2 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 8 Oct 2021 15:06:21 +0200 Subject: [PATCH 0400/1254] add JavaDoc --- .../app/csv/CSVMetadataImportReferenceIT.java | 19 +++++++++++++++++- .../repository/CollectionRestRepository.java | 20 +++++++++++++++++++ .../repository/EntityTypeRestRepository.java | 13 ++++++++++++ .../ExternalSourceRestRepository.java | 8 ++++++++ .../app/rest/EntityTypeRestRepositoryIT.java | 1 - .../config/spring/api/external-services.xml | 4 ++-- 6 files changed, 61 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java index df1a9e34d0..5933dff71c 100644 --- a/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java +++ b/dspace-api/src/test/java/org/dspace/app/csv/CSVMetadataImportReferenceIT.java @@ -55,6 +55,7 @@ import org.junit.Test; public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDatabase { //Common collection to utilize for test + private Collection col; private Collection col1; private Collection col2; @@ -76,6 +77,10 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat .withName("Parent Community") .build(); + col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + col1 = CollectionBuilder.createCollection(context, parentCommunity) .withEntityType("Person") .withName("Collection 1") @@ -83,7 +88,7 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat col2 = CollectionBuilder.createCollection(context, parentCommunity) .withEntityType("Publication") - .withName("Collection 1") + .withName("Collection 2") .build(); context.turnOffAuthorisationSystem(); @@ -149,6 +154,18 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat cleanupImportItems(items); } + @Test + public void testSingleMdRefIntoCollectionWithoutEntityTypeTest() throws Exception { + String[] csv = {"id,dspace.entity.type,relation.isAuthorOfPublication,collection,dc.identifier.other", + "+,Person,," + col.getHandle() + ",0", + "+,Publication,dc.identifier.other:0," + col.getHandle() + ",1"}; + Item[] items = runImport(csv); + assertRelationship(items[1], items[0], 1, "left", 0); + + // remove created items + cleanupImportItems(items); + } + /** * return an array of items given a representation of a CSV as a string array * 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 36c53fd33c..4a769709d3 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 @@ -222,6 +222,16 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository findSubmitAuthorizedByEntityType( @Parameter(value = "query") String query, @@ -244,6 +254,16 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository findSubmitAuthorizedByCommunityAndEntityType( @Parameter(value = "query") String query, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java index 341a8111e3..eec67b6fc9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java @@ -63,6 +63,12 @@ public class EntityTypeRestRepository extends DSpaceRestRepository findAllByAuthorizedCollection(Pageable pageable) { try { @@ -84,6 +90,13 @@ public class EntityTypeRestRepository extends DSpaceRestRepository findAllByAuthorizedExternalSource(Pageable pageable) { try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java index 474264520b..7888603f19 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java @@ -99,6 +99,14 @@ public class ExternalSourceRestRepository extends DSpaceRestRepository findByEntityType(Context context, Pageable pageable, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 9025855139..5ab815ef6d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -24,7 +24,6 @@ import org.dspace.builder.EntityTypeBuilder; import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.service.EntityTypeService; -import org.dspace.core.Constants; import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.external.service.ExternalDataService; import org.hamcrest.Matchers; diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 6d00073525..9e28e5d559 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -75,7 +75,7 @@ Publication - unset + none @@ -87,7 +87,7 @@ Publication - unset + none From 40e3291988f391d1b37657bb5164b6746a771169 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 8 Oct 2021 12:38:14 -0700 Subject: [PATCH 0401/1254] Work on test for iiif cache consumer. --- .../dspace/iiif/IIIFCacheEventConsumer.java | 4 +- .../app/rest/iiif/IIIFControllerIT.java | 106 ++++++++++++++++++ dspace/config/dspace.cfg | 2 +- 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java index 4346b11093..e721ae8eb0 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java @@ -29,7 +29,7 @@ public class IIIFCacheEventConsumer implements Consumer { private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFCacheEventConsumer.class); // Gets the service bean. - private final CacheEvictService cacheEvictService = CacheEvictBeanLocator.getCacheEvictService(); + private CacheEvictService cacheEvictService; // When true all entries will be cleared from cache. private boolean clearAll = false; @@ -40,7 +40,7 @@ public class IIIFCacheEventConsumer implements Consumer { @Override public void initialize() throws Exception { - + cacheEvictService = CacheEvictBeanLocator.getCacheEvictService(); } @Override 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 1fdf58e2e3..3915a99cf6 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 @@ -27,15 +27,20 @@ import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.service.ItemService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; public class IIIFControllerIT extends AbstractControllerIntegrationTest { public static final String IIIFBundle = "IIIF"; + @Autowired + ItemService itemService; + @Test public void disabledTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -1019,4 +1024,105 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { Matchers.containsString(bitstream2.getID() + "/content"))); } + + @Test + public void findOneWithCacheEvictionAfterItemUpdate() throws Exception { + + /** + * This test is broken. + * + * Our troubles are with the dispatcher and the way that flyway initializes the test environment. + * If we add "iiif" to the list of default dispatchers in dspace.cfg, the initialization + * triggers a call to the IIIFCacheEventConsumer before the web ApplicationContext has been injected. The + * CacheEvictService is not set in the consumer, and all subsequent calls to the consumer fail. If + * this root problem is solved and "iiif" can be added to the default dispatcher, then I think this test would + * succeed as written. (If possible this might be the best solution.) + * + * The test succeeds with the following code modifications. + * + * 1. Make sure the default dispatcher in dspace.cfg excludes the iiif consumer. + * 2. Add a new dispatcher definition to the test local.cfg that includes the iiif consumer. + * 3. Modify the web application's ContextUtil.initializeContext to add the new dispatcher to the context + * -- context.setDispatcher(iiif-dispatcher). + * + * The third change is obviously wrong, but it can be done temporarily to verify that the item is + * evicted from the cache when a patch is applied. Perhaps there's a reasonable way to set the test context + * in the web application that I don't know about. + * + * The alternative is to set the dispatcher here inside the test. Then instead of using the + * patch request, update the test Item using the dspace-api. The cache service is called and the cache + * is cleared. The manifest endpoint generates a new manifest as expected. But, the new response + * contains the old title. That doesn't seem right to me so there may be a way to make this approach work. + * + */ +// String patchRequestBody = +// "[{\"op\": \"replace\",\"path\": \"/metadata/dc.title/0/value\",\"value\": \"Public item 1 (revised)\"}]"; +// +// 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(); +// +// String bitstreamContent = "ThisIsSomeDummyText"; +// Bitstream bitstream1 = null; +// Bitstream bitstream2 = null; +// try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { +// bitstream1 = BitstreamBuilder +// .createBitstream(context, publicItem1, is) +// .withName("Bitstream1.jpg") +// .withMimeType("image/jpeg") +// .build(); +// } +// +// try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { +// bitstream2 = BitstreamBuilder +// .createBitstream(context, publicItem1, is) +// .withName("Bitstream2.png") +// .withMimeType("image/png") +// .build(); +// } +// +// context.restoreAuthSystemState(); +// +// // Default canvas size and label. +// getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.metadata[0].label", is("Title"))) +// .andExpect(jsonPath("$.metadata[0].value", is("Public item 1"))); +// +// +// String token = getAuthToken(admin.getEmail(), password); +// +// // The Item update should also remove the manifest from the cache. +// getClient(token).perform(patch("/api/core/items/" + publicItem1.getID()) +// .content(patchRequestBody) +// .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) +// .andExpect(status().isOk()); +// +//// context.turnOffAuthorisationSystem(); +//// List metadataList = publicItem1.getMetadata(); +//// metadataList.get(0).setValue("Public item 1 (revised)"); +//// publicItem1.setMetadata(metadataList); +//// itemService.update(context, publicItem1); +//// context.commit(); +//// context.restoreAuthSystemState(); +// +// // Verify that the updated title is in the manifest. +// getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.metadata[0].value", is("Public item 1 (revised)"))); + + + } + } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 1207c9ecff..9f68e10578 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -696,7 +696,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. -event.dispatcher.default.consumers = versioning, discovery, eperson, iiif +event.dispatcher.default.consumers = versioning, discovery, eperson # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher From 95b3b2816d295704b05e8c11ec6f1d96aadd1909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Mon, 11 Oct 2021 13:14:55 +0100 Subject: [PATCH 0402/1254] fix issue 3102 --- .../oai/metadataFormats/oai_openaire.xsl | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl index 0d566f1ce7..7b66eaf043 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/oai_openaire.xsl @@ -738,18 +738,22 @@ - - - + + + + + - - - + + + + + From 980ac528a26f69f85edf9f224e19e0085f439386 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 11 Oct 2021 15:07:08 +0200 Subject: [PATCH 0403/1254] Implement community feedbacks --- .../spring/spring-dspace-core-services.xml | 1 + .../config/spring/api/external-services.xml | 2 +- .../app/rest/EntityTypeRestRepositoryIT.java | 24 ++++++++++++------- .../rest/ExternalSourcesRestControllerIT.java | 3 +-- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml index 6dcaa43b08..cdea1c81fe 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml @@ -44,5 +44,6 @@ + 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 cf36e7fd25..ac163d3581 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 @@ -65,7 +65,7 @@ - Publication + Person diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 5ab815ef6d..5fe4abf728 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -24,6 +24,7 @@ import org.dspace.builder.EntityTypeBuilder; import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.service.EntityTypeService; +import org.dspace.core.Constants; import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.external.service.ExternalDataService; import org.hamcrest.Matchers; @@ -50,11 +51,13 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { //We expect a 200 OK status .andExpect(status().isOk()) //The type has to be 'discover' - .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalElements", is(8))) //There needs to be a self link to this endpoint .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, + Constants.ENTITY_TYPE_NONE)), EntityTypeMatcher .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), @@ -76,18 +79,19 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()) //The type has to be 'discover' .andExpect(jsonPath("$.page.size", is(5))) - .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalElements", is(8))) .andExpect(jsonPath("$.page.totalPages", is(2))) //There needs to be a self link to this endpoint .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, + Constants.ENTITY_TYPE_NONE)), EntityTypeMatcher .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")) + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")) ))); getClient().perform(get("/api/core/entitytypes").param("size", "5").param("page", "1")) @@ -96,13 +100,14 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()) //The type has to be 'discover' .andExpect(jsonPath("$.page.size", is(5))) - .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalElements", is(8))) .andExpect(jsonPath("$.page.totalPages", is(2))) .andExpect(jsonPath("$.page.number", is(1))) //There needs to be a self link to this endpoint .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), EntityTypeMatcher .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalVolume")), EntityTypeMatcher @@ -133,7 +138,8 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")) + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, + Constants.ENTITY_TYPE_NONE)) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/entitytypes?"), @@ -148,7 +154,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { Matchers.containsString("/api/core/entitytypes?"), Matchers.containsString("page=2"), Matchers.containsString("size=3")))) .andExpect(jsonPath("$.page.size", is(3))) - .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalElements", is(8))) .andExpect(jsonPath("$.page.totalPages", is(3))) .andExpect(jsonPath("$.page.number", is(0))); @@ -159,7 +165,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalVolume")) + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/entitytypes?"), @@ -177,7 +183,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { Matchers.containsString("/api/core/entitytypes?"), Matchers.containsString("page=2"), Matchers.containsString("size=3")))) .andExpect(jsonPath("$.page.size", is(3))) - .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalElements", is(8))) .andExpect(jsonPath("$.page.totalPages", is(3))) .andExpect(jsonPath("$.page.number", is(1))); } 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 1a9db10df1..c974c89390 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 @@ -145,10 +145,9 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( ExternalSourceMatcher.matchExternalSource("mock", "mock", false), - ExternalSourceMatcher.matchExternalSource("orcid", "orcid", false), ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))); getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Journal")) From 61025c1c95a6897a02f148420985e4a5baddc33a Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 11 Oct 2021 15:57:12 +0200 Subject: [PATCH 0404/1254] 83707: Fix parameter types of harvest script config --- .../dspace/app/harvest/HarvestScriptConfiguration.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java index 982973e47c..6290562143 100644 --- a/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/harvest/HarvestScriptConfiguration.java @@ -43,14 +43,22 @@ public class HarvestScriptConfiguration extends ScriptConfigu public Options getOptions() { Options options = new Options(); options.addOption("p", "purge", false, "delete all items in the collection"); + options.getOption("p").setType(boolean.class); options.addOption("r", "run", false, "run the standard harvest procedure"); + options.getOption("r").setType(boolean.class); options.addOption("g", "ping", false, "test the OAI server and set"); + options.getOption("g").setType(boolean.class); options.addOption("s", "setup", false, "Set the collection up for harvesting"); + options.getOption("s").setType(boolean.class); options.addOption("S", "start", false, "start the harvest loop"); + options.getOption("S").setType(boolean.class); options.addOption("R", "reset", false, "reset harvest status on all collections"); + options.getOption("R").setType(boolean.class); options.addOption("P", "purgeCollections", false, "purge all harvestable collections"); + options.getOption("P").setType(boolean.class); options.addOption("o", "reimport", false, "reimport all items in the collection, " + "this is equivalent to -p -r, purging all items in a collection and reimporting them"); + options.getOption("o").setType(boolean.class); options.addOption("c", "collection", true, "harvesting collection (handle or id)"); options.addOption("t", "type", true, @@ -64,6 +72,7 @@ public class HarvestScriptConfiguration extends ScriptConfigu "crosswalk in dspace.cfg"); options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); return options; } From cbdebd3e97a4bdd225992536e1b0f4cb13ffc8a7 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 11 Oct 2021 16:08:06 +0200 Subject: [PATCH 0405/1254] fix LGTM notes --- .../EntityTypeRelationshipLinkRepository.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java index 7d70c37282..3d71ddd9bb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRelationshipLinkRepository.java @@ -44,11 +44,11 @@ public class EntityTypeRelationshipLinkRepository extends AbstractDSpaceRestRepo * This method will retrieve all the RelationshipTypes that conform * to the given EntityType by the given ID and it will return this in a wrapped resource. * - * @param request The request object - * @param id The ID of the EntityType objects that we'll use to retrieve the RelationshipTypes - * @param pageable The pagination object - * @param projection - * @return List of RelationshipType objects as defined above + * @param request The request object + * @param id The ID of the EntityType objects that we'll use to retrieve the RelationshipTypes + * @param optionalPageable The pagination object + * @param projection The current Projection + * @return List of RelationshipType objects as defined above */ public Page getEntityTypeRelationship(@Nullable HttpServletRequest request, Integer id, From 2f74887c8f379b78e0212b603ad7410f5d1b6f4f Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 13 Oct 2021 10:05:00 -0700 Subject: [PATCH 0406/1254] Updated iiif configuation. --- dspace-server-webapp/pom.xml | 2 +- .../iiif/service/AbstractResourceService.java | 4 +- dspace/config/modules/iiif.cfg | 39 ++++++------------- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 722944b116..27844682bf 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -497,7 +497,7 @@ de.digitalcollections.iiif iiif-apis - 0.3.7 + 0.3.9 org.javassist diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java index 8501b97189..71da08d37d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java @@ -58,10 +58,10 @@ public abstract class AbstractResourceService { * @param configurationService the DSpace configuration service */ protected void setConfiguration(ConfigurationService configurationService) { - IIIF_ENDPOINT = configurationService.getProperty("iiif.url"); + IIIF_ENDPOINT = configurationService.getProperty("dspace.server.url") + "/iiif/"; IMAGE_SERVICE = configurationService.getProperty("iiif.image.server"); SEARCH_URL = configurationService.getProperty("iiif.search.url"); - BITSTREAM_PATH_PREFIX = configurationService.getProperty("iiif.bitstream.url"); + BITSTREAM_PATH_PREFIX = configurationService.getProperty("dspace.server.url") + "/api/core/bitstreams"; DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint"); CLIENT_URL = configurationService.getProperty("dspace.ui.url"); IIIF_LOGO_IMAGE = configurationService.getProperty("iiif.logo.image"); diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index 21a9692316..53d3ca98c6 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -1,25 +1,18 @@ #### IIIF CONFIGURATION #### -# Base URL of the IIIF API endpoint. This is used when -# setting annotation identifiers. Some identifiers are used for -# linking and must reference a valid IIIF API endpoint. -iiif.url = ${dspace.server.url}/iiif/ - -# Base URL of the IIIF image server. +# Public base url of a iiif server able to serve DSpace images. The bitstream +# uuid is appended to this URL iiif.image.server = http://localhost:8182/iiif/2/ -# Base URL of the search service used to support the optional IIIF Search -# capability. The actual path will depend on how search is supported. -# (experimental) -# iiif.search.url = ${solr.server}/solr/word_highlighting +# Base URL of the search service used to support the (experimental) IIIF Search +# capability. The actual path will depend on how search is being supported. +# This example assumes the solr plugin https://dbmdz.github.io/solr-ocrhighlighting/ +# iiif.search.url = ${solr.server}/word_highlighting -# The search plugin used to support IIIF Search. (experimental) +# The search plugin used to support (experimental) IIIF Search. +# This is the class used with https://dbmdz.github.io/solr-ocrhighlighting/ +# It is currently the only supported option. # iiif.search.plugin = org.dspace.app.rest.iiif.service.WordHighlightSolrSearch -# Required path prefix for bitstream content. This is used for creating -# the path for retrieving bitstreams for "rendering" -# and "seeAlso" annotations via the DSpace API. -iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams - # Sets the viewing hint. Possible values: "paged" or "individuals". # Typically "paged" is preferred for multi-age documents. Use "individuals" # if you plan to implement the search api. @@ -72,21 +65,13 @@ iiif.license.uri = dc.rights.uri # static text to be used as attribution in the iiif manifests iiif.attribution = ${dspace.name} +# URL for logo. If defined, the logo will be added to the manifest. iiif.logo.image = ${dspace.ui.url}/assets/images/dspace-logo.svg -# (optional) one of individuals, paged or continuous. Can be overridden at the item level via + +# (optional) one of individuals, paged or continuous. Can be overridden at the item level via # the iiif.view.hint metadata iiif.document.viewing.hint = individuals - - -# public base url of a iiif server able to serve DSpace images. The bitstream uuid is appended to this URL -iiif.image.server = - -# these should be not changed. They must match the routing configuration for the iiif controller -iiif.url = ${dspace.server.url}/iiif -iiif.solr.search.url = -iiif.bitstream.url = ${dspace.server.url}/api/core/bitstreams - # default value for the canvas size. Can be overridden at the item, bundle or bitstream level # via the iiif.image.width e iiif.image.height metadata # iiif.canvas.default-width = 1200 From 3870e362c3b2a2ad05f8ed0d3f501c66f6da8432 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 13 Oct 2021 10:22:21 -0700 Subject: [PATCH 0407/1254] Javadoc for iiif interfaces. --- .../org/dspace/app/rest/iiif/model/generator/IIIFResource.java | 3 +++ .../org/dspace/app/rest/iiif/model/generator/IIIFService.java | 3 +++ .../org/dspace/app/rest/iiif/model/generator/IIIFValue.java | 3 +++ 3 files changed, 9 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java index cdf9a5794a..53bf23e1a1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java @@ -9,6 +9,9 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.sharedcanvas.Resource; +/** + * Interface for iiif resource generators. + */ public interface IIIFResource { /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java index e06c9ebb6d..bbcc206757 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java @@ -9,6 +9,9 @@ package org.dspace.app.rest.iiif.model.generator; import de.digitalcollections.iiif.model.Service; +/** + * Interface for iiif service generators. + */ public interface IIIFService { /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java index f88e3ed0b1..cc0685c135 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest.iiif.model.generator; +/** + * Interface for iiif value generators. + */ public interface IIIFValue { /** From 6bedbf09a412f33ac258fc44e629e65ad47fdd2d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 13 Oct 2021 14:26:52 -0400 Subject: [PATCH 0408/1254] Ensure that a mapped Item is exported only once. #7988 --- .../MetadataDSpaceCsvExportServiceImpl.java | 36 +++--- .../MetadataDSpaceCsvExportServiceImplIT.java | 115 ++++++++++++++++++ 2 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/content/MetadataDSpaceCsvExportServiceImplIT.java diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java index 7dad2117d5..8bc34d3f5e 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -9,12 +9,12 @@ package org.dspace.content; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.UUID; -import com.google.common.collect.Iterators; import org.dspace.app.bulkedit.DSpaceCSV; import org.dspace.app.util.service.DSpaceObjectUtils; import org.dspace.content.service.ItemService; @@ -102,40 +102,36 @@ public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExpo } /** - * Build an array list of item ids that are in a community (include sub-communities and collections) + * Build a Java Collection of item IDs that are in a Community (including + * its sub-Communities and Collections) * * @param context DSpace context * @param community The community to build from - * @return The list of item ids + * @return Iterator over the Collection of item ids * @throws SQLException if database error */ private Iterator buildFromCommunity(Context context, Community community) throws SQLException { + Set result = new HashSet<>(); + // Add all the collections List collections = community.getCollections(); - Iterator result = Collections.emptyIterator(); for (Collection collection : collections) { Iterator items = itemService.findByCollection(context, collection); - result = addItemsToResult(result, items); - + while (items.hasNext()) { + result.add(items.next()); + } } - // Add all the sub-communities + + // Add all the sub-communities List communities = community.getSubcommunities(); for (Community subCommunity : communities) { Iterator items = buildFromCommunity(context, subCommunity); - result = addItemsToResult(result, items); + while (items.hasNext()) { + result.add(items.next()); + } } - return result; - } - - private Iterator addItemsToResult(Iterator result, Iterator items) { - if (result == null) { - result = items; - } else { - result = Iterators.concat(result, items); - } - - return result; + return result.iterator(); } } diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataDSpaceCsvExportServiceImplIT.java b/dspace-api/src/test/java/org/dspace/content/MetadataDSpaceCsvExportServiceImplIT.java new file mode 100644 index 0000000000..c2d4f56ca6 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/content/MetadataDSpaceCsvExportServiceImplIT.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.content; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Iterator; +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.app.bulkedit.DSpaceCSVLine; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.service.MetadataDSpaceCsvExportService; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.utils.DSpace; +import org.junit.Ignore; +import org.junit.Test; + +/** + * + * @author Mark H. Wood + */ +public class MetadataDSpaceCsvExportServiceImplIT + extends AbstractIntegrationTestWithDatabase { + /** + * Test of handleExport method, of class MetadataDSpaceCsvExportServiceImpl. + * @throws java.lang.Exception passed through. + */ + @Ignore + @Test + public void testHandleExport() + throws Exception { + System.out.println("handleExport"); + boolean exportAllItems = false; + boolean exportAllMetadata = false; + String identifier = ""; + DSpaceRunnableHandler handler = null; + MetadataDSpaceCsvExportServiceImpl instance = new MetadataDSpaceCsvExportServiceImpl(); + DSpaceCSV expResult = null; + DSpaceCSV result = instance.handleExport(context, exportAllItems, + exportAllMetadata, identifier, handler); + assertEquals(expResult, result); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of export method, of class MetadataDSpaceCsvExportServiceImpl. + * @throws java.lang.Exception passed through. + */ + @Ignore + @Test + public void testExport_3args_1() + throws Exception { + System.out.println("export"); + Iterator toExport = null; + boolean exportAll = false; + MetadataDSpaceCsvExportServiceImpl instance = new MetadataDSpaceCsvExportServiceImpl(); + DSpaceCSV expResult = null; + DSpaceCSV result = instance.export(context, toExport, exportAll); + assertEquals(expResult, result); + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + + /** + * Test of export with mapped Item. + * @throws java.lang.Exception passed through. + */ + @Test + public void testMappedItem() + throws Exception { + System.out.println("export"); + + // Create some content with which to test. + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + Collection collection1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection1") + .build(); + Collection collection2 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection2") + .build(); + context.setCurrentUser(eperson); + Item item = ItemBuilder.createItem(context, collection1) + .withTitle("Item") + .withIssueDate("1957") + .build(); + item.addCollection(collection2); + context.restoreAuthSystemState(); + + // Test! + MetadataDSpaceCsvExportService instance = new DSpace() + .getServiceManager() + .getServiceByName(MetadataDSpaceCsvExportServiceImpl.class.getCanonicalName(), + MetadataDSpaceCsvExportService.class); + DSpaceCSV result = instance.export(context, parentCommunity, false); + + // Examine the result. + List csvLines = result.getCSVLines(); + assertEquals("One item mapped twice should produce one line", + 1, csvLines.size()); + } +} From 70de1bc61c20a1a45db8016c3d7c59d06662c00d Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 13 Oct 2021 18:35:33 -0700 Subject: [PATCH 0409/1254] Moved iiif to separate module. --- dspace-iiif/pom.xml | 121 ++++++++++++++ .../org/dspace/app/iiif}/IIIFController.java | 5 +- .../dspace/app}/iiif/IIIFServiceFacade.java | 12 +- .../dspace/app/iiif/dspace/ContextUtil.java | 158 ++++++++++++++++++ .../exception/NotImplementedException.java | 2 +- .../app}/iiif/model/ObjectMapperFactory.java | 2 +- .../model/generator/AnnotationGenerator.java | 2 +- .../generator/AnnotationListGenerator.java | 4 +- .../model/generator/BehaviorGenerator.java | 2 +- .../iiif/model/generator/CanvasGenerator.java | 2 +- .../model/generator/CanvasItemsGenerator.java | 6 +- .../generator/ContentAsTextGenerator.java | 2 +- .../generator/ContentSearchGenerator.java | 2 +- .../generator/ExternalLinksGenerator.java | 2 +- .../iiif/model/generator/IIIFResource.java | 2 +- .../iiif/model/generator/IIIFService.java | 2 +- .../app}/iiif/model/generator/IIIFValue.java | 2 +- .../generator/ImageContentGenerator.java | 4 +- .../generator/ImageServiceGenerator.java | 2 +- .../model/generator/ManifestGenerator.java | 2 +- .../generator/MetadataEntryGenerator.java | 2 +- .../model/generator/ProfileGenerator.java | 2 +- .../generator/PropertyValueGenerator.java | 2 +- .../iiif/model/generator/RangeGenerator.java | 6 +- .../generator/SearchResultGenerator.java | 2 +- .../iiif/service/AbstractResourceService.java | 8 +- .../iiif/service/AnnotationListService.java | 10 +- .../iiif/service/CanvasLookupService.java | 6 +- .../app}/iiif/service/CanvasService.java | 10 +- .../iiif/service/ImageContentService.java | 8 +- .../app}/iiif/service/ManifestService.java | 14 +- .../app}/iiif/service/RangeService.java | 4 +- .../app}/iiif/service/RelatedService.java | 4 +- .../iiif/service/SearchAnnotationService.java | 2 +- .../app}/iiif/service/SearchService.java | 4 +- .../app}/iiif/service/SeeAlsoService.java | 6 +- .../app}/iiif/service/SequenceService.java | 8 +- .../iiif/service/WordHighlightSolrSearch.java | 14 +- .../BitstreamBytesIIIFVirtualMetadata.java | 2 +- .../BitstreamChecksumIIIFVirtualMetadata.java | 2 +- .../BitstreamFormatIIIFVirtualMetadata.java | 2 +- .../utils}/BitstreamIIIFVirtualMetadata.java | 2 +- .../BitstreamMimetypeIIIFVirtualMetadata.java | 2 +- .../app/iiif/service/utils}/IIIFUtils.java | 4 +- .../iiif/service/utils}/ImageProfileUtil.java | 4 +- .../iiif/service/utils}/ThumbProfileUtil.java | 4 +- dspace-server-webapp/pom.xml | 37 +--- .../java/org/dspace/app/rest/Application.java | 2 + .../rest/{iiif => }/cache/CacheLogger.java | 5 +- .../app/rest/iiif/cache/CacheConfig.java | 24 --- .../app/rest/utils/ApplicationConfig.java | 2 +- .../src/main/resources/iiif/cache/ehcache.xml | 2 +- dspace/pom.xml | 5 + pom.xml | 20 +++ 54 files changed, 411 insertions(+), 156 deletions(-) create mode 100644 dspace-iiif/pom.xml rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app/iiif}/IIIFController.java (97%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/IIIFServiceFacade.java (92%) create mode 100644 dspace-iiif/src/main/java/org/dspace/app/iiif/dspace/ContextUtil.java rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/exception/NotImplementedException.java (93%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/ObjectMapperFactory.java (96%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/AnnotationGenerator.java (98%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/AnnotationListGenerator.java (92%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/BehaviorGenerator.java (94%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/CanvasGenerator.java (98%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/CanvasItemsGenerator.java (90%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/ContentAsTextGenerator.java (94%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/ContentSearchGenerator.java (98%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/ExternalLinksGenerator.java (97%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/IIIFResource.java (90%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/IIIFService.java (90%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/IIIFValue.java (89%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/ImageContentGenerator.java (92%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/ImageServiceGenerator.java (95%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/ManifestGenerator.java (99%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/MetadataEntryGenerator.java (96%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/ProfileGenerator.java (95%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/PropertyValueGenerator.java (94%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/RangeGenerator.java (94%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/model/generator/SearchResultGenerator.java (96%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/AbstractResourceService.java (92%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/AnnotationListService.java (92%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/CanvasLookupService.java (92%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/CanvasService.java (95%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/ImageContentService.java (90%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/ManifestService.java (96%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/RangeService.java (92%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/RelatedService.java (91%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/SearchAnnotationService.java (91%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/SearchService.java (95%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/SeeAlsoService.java (87%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/SequenceService.java (94%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest => dspace-iiif/src/main/java/org/dspace/app}/iiif/service/WordHighlightSolrSearch.java (96%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils}/BitstreamBytesIIIFVirtualMetadata.java (95%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils}/BitstreamChecksumIIIFVirtualMetadata.java (95%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils}/BitstreamFormatIIIFVirtualMetadata.java (95%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils}/BitstreamIIIFVirtualMetadata.java (93%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils}/BitstreamMimetypeIIIFVirtualMetadata.java (95%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils}/IIIFUtils.java (99%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils}/ImageProfileUtil.java (86%) rename {dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util => dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils}/ThumbProfileUtil.java (89%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/{iiif => }/cache/CacheLogger.java (87%) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml new file mode 100644 index 0000000000..ffe8f36a1e --- /dev/null +++ b/dspace-iiif/pom.xml @@ -0,0 +1,121 @@ + + 4.0.0 + org.dspace + dspace-iiif + jar + DSpace IIIF + + DSpace IIIF Extension + + + + + org.dspace + dspace-parent + 7.1-SNAPSHOT + .. + + + + + ${basedir}/.. + + @ + + + + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-data-rest + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-security + ${spring-boot.version} + + + + org.springframework.boot + spring-boot-starter-cache + ${spring-boot.version} + + + javax.cache + cache-api + 1.1.0 + + + + org.ehcache + ehcache + 3.4.0 + + + + + + de.digitalcollections.iiif + iiif-apis + 0.3.9 + + + org.javassist + javassist + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.springframework.security + spring-security-core + + + org.dmfs + iterators + + + com.fasterxml.jackson.module + jackson-module-parameter-names + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + + + org.dspace + dspace-api + + + + + + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java similarity index 97% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java index 0414419bc1..b0e4130f10 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IIIFController.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java @@ -5,12 +5,11 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest; +package org.dspace.app.iiif; import java.util.UUID; -import org.dspace.app.rest.iiif.IIIFServiceFacade; -import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.iiif.dspace.ContextUtil; import org.dspace.core.Context; import org.dspace.services.RequestService; import org.dspace.utils.DSpace; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFServiceFacade.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFServiceFacade.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java index 22ef973cbf..cf0d75c391 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/IIIFServiceFacade.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java @@ -5,16 +5,16 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif; +package org.dspace.app.iiif; import java.sql.SQLException; import java.util.UUID; -import org.dspace.app.rest.iiif.service.AnnotationListService; -import org.dspace.app.rest.iiif.service.CanvasLookupService; -import org.dspace.app.rest.iiif.service.ManifestService; -import org.dspace.app.rest.iiif.service.SearchService; -import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.iiif.service.AnnotationListService; +import org.dspace.app.iiif.service.CanvasLookupService; +import org.dspace.app.iiif.service.ManifestService; +import org.dspace.app.iiif.service.SearchService; +import org.dspace.app.iiif.service.utils.IIIFUtils; import org.dspace.content.Item; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/dspace/ContextUtil.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/dspace/ContextUtil.java new file mode 100644 index 0000000000..3f90a98068 --- /dev/null +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/dspace/ContextUtil.java @@ -0,0 +1,158 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.iiif.dspace; + +import java.sql.SQLException; +import java.util.Enumeration; +import java.util.Locale; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Context; +import org.dspace.core.I18nUtil; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.dspace.utils.DSpace; + +public class ContextUtil { + /** + * Where the context is stored on an HTTP Request object + */ + public static final String DSPACE_CONTEXT = "dspace.context"; + + /** + * Default constructor + */ + private ContextUtil() { + } + + /** + * Shortcut for {@link #obtainContext(Request)} using the {@link RequestService} + * to retrieve the current thread request + * + * @return the DSpace Context associated with the current thread-bound request + */ + public static Context obtainCurrentRequestContext() { + Context context = null; + RequestService requestService = new DSpace().getRequestService(); + Request currentRequest = requestService.getCurrentRequest(); + if (currentRequest != null) { + context = ContextUtil.obtainContext(currentRequest.getHttpServletRequest()); + } + return context; + } + + /** + * Obtain a new context object. If a context object has already been created + * for this HTTP request, it is re-used, otherwise it is created. + * + * @param request the servlet request object + * @return a context object + */ + public static Context obtainContext(HttpServletRequest request) { + Context context = (Context) request.getAttribute(DSPACE_CONTEXT); + + if (context == null) { + try { + context = ContextUtil.initializeContext(); + } catch (SQLException e) { + //log.error("Unable to initialize context", e); + return null; + } + + // Store the context in the request + request.setAttribute(DSPACE_CONTEXT, context); + } + // this need to be verified each time that the context is extracted from the request + // as some call happen before that the login process is completed and user settings can + // change the locale + Locale currentLocale = getLocale(context, request); + context.setCurrentLocale(currentLocale); + return context; + } + + private static Locale getLocale(Context context, HttpServletRequest request) { + Locale userLocale = null; + Locale supportedLocale = null; + + // Locales requested from client + String locale = request.getHeader("Accept-Language"); + if (StringUtils.isNotBlank(locale)) { + Enumeration locales = request.getLocales(); + if (locales != null) { + while (locales.hasMoreElements()) { + Locale current = locales.nextElement(); + if (I18nUtil.isSupportedLocale(current)) { + userLocale = current; + break; + } + } + } + } + if (userLocale == null && context.getCurrentUser() != null) { + String userLanguage = context.getCurrentUser().getLanguage(); + if (userLanguage != null) { + userLocale = new Locale(userLanguage); + } + } + if (userLocale == null) { + return I18nUtil.getDefaultLocale(); + } + supportedLocale = I18nUtil.getSupportedLocale(userLocale); + return supportedLocale; + } + + + /** + * Initialize a new Context object + * + * @return a DSpace Context Object + * @throws SQLException + */ + private static Context initializeContext() throws SQLException { + // Create a new Context + Context context = new Context(); + + // Set the session ID + /**context.setExtraLogInfo("session_id=" + + request.getSession().getId()); + + AuthenticationUtil.resumeLogin(context, request); + + // Set any special groups - invoke the authentication mgr. + int[] groupIDs = AuthenticationManager.getSpecialGroups(context, request); + + for (int i = 0; i < groupIDs.length; i++) + { + context.setSpecialGroup(groupIDs[i]); + log.debug("Adding Special Group id="+String.valueOf(groupIDs[i])); + } + + // Set the session ID and IP address + String ip = request.getRemoteAddr(); + if (useProxies == null) { + useProxies = ConfigurationManager.getBooleanProperty("useProxies", false); + } + if(useProxies && request.getHeader("X-Forwarded-For") != null) + { + // This header is a comma delimited list + for(String xfip : request.getHeader("X-Forwarded-For").split(",")) + { + if(!request.getHeader("X-Forwarded-For").contains(ip)) + { + ip = xfip.trim(); + } + } + } + context.setExtraLogInfo("session_id=" + request.getSession().getId() + ":ip_addr=" + ip); + */ + + return context; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/exception/NotImplementedException.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/exception/NotImplementedException.java similarity index 93% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/exception/NotImplementedException.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/exception/NotImplementedException.java index d8574ef4f9..518794bb7b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/exception/NotImplementedException.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/exception/NotImplementedException.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.exception; +package org.dspace.app.iiif.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/ObjectMapperFactory.java similarity index 96% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/ObjectMapperFactory.java index f36ed105da..1e5183fd10 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/ObjectMapperFactory.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/ObjectMapperFactory.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model; +package org.dspace.app.iiif.model; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationGenerator.java similarity index 98% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationGenerator.java index fbe7ca8a20..3947df3533 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationListGenerator.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationListGenerator.java index a01a8aabf6..da977a5ccc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/AnnotationListGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/AnnotationListGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; @@ -27,7 +27,7 @@ import org.springframework.web.context.annotation.RequestScope; */ @RequestScope @Component -public class AnnotationListGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { +public class AnnotationListGenerator implements IIIFResource { private String identifier; private List annotations = new ArrayList<>(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/BehaviorGenerator.java similarity index 94% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/BehaviorGenerator.java index df275e8e02..3d3fccd1fa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/BehaviorGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/BehaviorGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import de.digitalcollections.iiif.model.enums.ViewingHint; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java similarity index 98% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java index bb71416d68..096abfea58 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java similarity index 90% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java index e23a7ffc7f..d7b6bdf0ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; @@ -49,7 +49,7 @@ public class CanvasItemsGenerator implements IIIFResource { * for display or download by a human user. This is typically going to be a PDF file. * @param otherContent generator for the resource */ - public void addRendering(org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator otherContent) { + public void addRendering(ExternalLinksGenerator otherContent) { this.renderings.add((OtherContent) otherContent.generateResource()); } @@ -57,7 +57,7 @@ public class CanvasItemsGenerator implements IIIFResource { * Adds a single {@code Canvas} to the sequence. * @param canvas generator for canvas */ - public String addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { + public String addCanvas(CanvasGenerator canvas) { Canvas resource = (Canvas) canvas.generateResource(); this.canvas.add(resource); return resource.getIdentifier().toString(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentAsTextGenerator.java similarity index 94% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentAsTextGenerator.java index 520acd0c1f..e83c34bfce 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentAsTextGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentAsTextGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import de.digitalcollections.iiif.model.openannotation.ContentAsText; import de.digitalcollections.iiif.model.sharedcanvas.Resource; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentSearchGenerator.java similarity index 98% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentSearchGenerator.java index 6ee1c23732..28cc13c07d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ContentSearchGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ContentSearchGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.net.URI; import java.net.URISyntaxException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ExternalLinksGenerator.java similarity index 97% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ExternalLinksGenerator.java index 3c4ec7e72f..94c1828375 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ExternalLinksGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ExternalLinksGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import javax.validation.constraints.NotNull; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFResource.java similarity index 90% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFResource.java index 53bf23e1a1..7599349fcb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFResource.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFResource.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import de.digitalcollections.iiif.model.sharedcanvas.Resource; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFService.java similarity index 90% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFService.java index bbcc206757..33f7b7e1d5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFService.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import de.digitalcollections.iiif.model.Service; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFValue.java similarity index 89% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFValue.java index cc0685c135..dbaf9afcbc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/IIIFValue.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/IIIFValue.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; /** * Interface for iiif value generators. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java index 373af5b39e..610ec571d9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageContentGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import javax.validation.constraints.NotNull; @@ -19,7 +19,7 @@ import de.digitalcollections.iiif.model.sharedcanvas.Resource; * field of annotations with motivation "sc:painting". Image resources, and only image resources, * are included in the image's property of the canvas. This changes in API version 3.0. */ -public class ImageContentGenerator implements IIIFResource { +public class ImageContentGenerator implements IIIFResource { private final ImageContent imageContent; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageServiceGenerator.java similarity index 95% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageServiceGenerator.java index 011d417862..9a39945116 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ImageServiceGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageServiceGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import de.digitalcollections.iiif.model.Service; import de.digitalcollections.iiif.model.image.ImageService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java similarity index 99% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java index 1e66c579fe..4fa1d00ea8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ManifestGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.net.URI; import java.util.ArrayList; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/MetadataEntryGenerator.java similarity index 96% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/MetadataEntryGenerator.java index 1a3fbbf9c9..bab4dbecae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/MetadataEntryGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/MetadataEntryGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import de.digitalcollections.iiif.model.MetadataEntry; import de.digitalcollections.iiif.model.PropertyValue; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ProfileGenerator.java similarity index 95% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ProfileGenerator.java index 947d5cfbb0..4c497763a6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/ProfileGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ProfileGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.net.URI; import java.net.URISyntaxException; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/PropertyValueGenerator.java similarity index 94% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/PropertyValueGenerator.java index 2124580c65..2568d715a3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/PropertyValueGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/PropertyValueGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import de.digitalcollections.iiif.model.PropertyValue; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java similarity index 94% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java index 54ff794d47..cc358788e2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/RangeGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; @@ -14,7 +14,7 @@ import javax.validation.constraints.NotNull; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; -import org.dspace.app.rest.iiif.service.RangeService; +import org.dspace.app.iiif.service.RangeService; /** * This generator wraps the domain model for IIIF {@code ranges}. @@ -25,7 +25,7 @@ import org.dspace.app.rest.iiif.service.RangeService; * This is used to populate the "structures" element of the Manifest. The structure is derived from the iiif.toc * metadata and the ordered sequence of bitstreams (canvases) */ -public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { +public class RangeGenerator implements IIIFResource { private String identifier; private String label; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/SearchResultGenerator.java similarity index 96% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/SearchResultGenerator.java index 46ccb68fc2..f1eac30906 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/model/generator/SearchResultGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/SearchResultGenerator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.model.generator; +package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AbstractResourceService.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/AbstractResourceService.java index 71da08d37d..c7ef01984f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AbstractResourceService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AbstractResourceService.java @@ -5,13 +5,13 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.util.UUID; -import org.dspace.app.rest.iiif.service.util.IIIFUtils; -import org.dspace.app.rest.iiif.service.util.ImageProfileUtil; -import org.dspace.app.rest.iiif.service.util.ThumbProfileUtil; +import org.dspace.app.iiif.service.utils.IIIFUtils; +import org.dspace.app.iiif.service.utils.ImageProfileUtil; +import org.dspace.app.iiif.service.utils.ThumbProfileUtil; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AnnotationListService.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/AnnotationListService.java index 4094b74900..fa71fae70f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/AnnotationListService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AnnotationListService.java @@ -5,16 +5,16 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.sql.SQLException; import java.util.List; import java.util.UUID; -import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; -import org.dspace.app.rest.iiif.model.generator.AnnotationListGenerator; -import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; -import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.iiif.model.generator.AnnotationListGenerator; +import org.dspace.app.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.iiif.service.utils.IIIFUtils; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasLookupService.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasLookupService.java index 859f6cd7c1..5c900d0a1c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasLookupService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasLookupService.java @@ -5,12 +5,12 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.sql.SQLException; -import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; -import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.iiif.model.generator.CanvasGenerator; +import org.dspace.app.iiif.service.utils.IIIFUtils; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java similarity index 95% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java index eb4d192b88..1dac7da8bf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/CanvasService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.util.ArrayList; import java.util.List; @@ -13,10 +13,10 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; -import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; -import org.dspace.app.rest.iiif.service.util.BitstreamIIIFVirtualMetadata; -import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.iiif.model.generator.CanvasGenerator; +import org.dspace.app.iiif.model.generator.ImageContentGenerator; +import org.dspace.app.iiif.service.utils.BitstreamIIIFVirtualMetadata; +import org.dspace.app.iiif.service.utils.IIIFUtils; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ImageContentService.java similarity index 90% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/ImageContentService.java index 7b8a5ec011..3ef28d91c6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ImageContentService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ImageContentService.java @@ -5,13 +5,13 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.util.UUID; -import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; -import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator; -import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.dspace.app.iiif.model.generator.ImageContentGenerator; +import org.dspace.app.iiif.model.generator.ImageServiceGenerator; +import org.dspace.app.iiif.model.generator.ProfileGenerator; import org.dspace.services.ConfigurationService; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java similarity index 96% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java index 95fa0cd044..7dcb474a75 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/ManifestService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.sql.SQLException; import java.util.ArrayList; @@ -15,12 +15,12 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; -import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator; -import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; -import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; -import org.dspace.app.rest.iiif.model.generator.RangeGenerator; -import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.iiif.model.generator.CanvasGenerator; +import org.dspace.app.iiif.model.generator.ContentSearchGenerator; +import org.dspace.app.iiif.model.generator.ImageContentGenerator; +import org.dspace.app.iiif.model.generator.ManifestGenerator; +import org.dspace.app.iiif.model.generator.RangeGenerator; +import org.dspace.app.iiif.service.utils.IIIFUtils; import org.dspace.app.util.service.MetadataExposureService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java index f7ad38ff32..54ec70ec0f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RangeService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java @@ -5,9 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; -import org.dspace.app.rest.iiif.model.generator.RangeGenerator; +import org.dspace.app.iiif.model.generator.RangeGenerator; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RelatedService.java similarity index 91% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/RelatedService.java index 3b6596c102..94550172ee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/RelatedService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RelatedService.java @@ -5,9 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; -import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.iiif.model.generator.ExternalLinksGenerator; import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchAnnotationService.java similarity index 91% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchAnnotationService.java index f97e794f23..776e399df4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchAnnotationService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchAnnotationService.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.util.UUID; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchService.java similarity index 95% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchService.java index d7931c4979..22617ebd4f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SearchService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchService.java @@ -5,13 +5,13 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.util.List; import java.util.UUID; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.iiif.exception.NotImplementedException; +import org.dspace.app.iiif.exception.NotImplementedException; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SeeAlsoService.java similarity index 87% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/SeeAlsoService.java index 63651efd90..f4eb22ce88 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SeeAlsoService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SeeAlsoService.java @@ -5,10 +5,10 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; -import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; -import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.iiif.model.generator.ExternalLinksGenerator; import org.dspace.content.Item; import org.dspace.services.ConfigurationService; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java similarity index 94% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java index a4ed5c7ec7..a37088fc5e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/SequenceService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java @@ -5,15 +5,15 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.sql.SQLException; import java.util.List; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; -import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; -import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; +import org.dspace.app.iiif.model.generator.CanvasGenerator; +import org.dspace.app.iiif.model.generator.CanvasItemsGenerator; +import org.dspace.app.iiif.model.generator.ExternalLinksGenerator; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java similarity index 96% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java index fc24313b92..4763284334 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service; +package org.dspace.app.iiif.service; import java.io.IOException; import java.net.URLEncoder; @@ -29,12 +29,12 @@ import org.apache.solr.client.solrj.impl.NoOpResponseParser; import org.apache.solr.client.solrj.request.QueryRequest; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.util.NamedList; -import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator; -import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; -import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator; -import org.dspace.app.rest.iiif.model.generator.ManifestGenerator; -import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator; -import org.dspace.app.rest.iiif.service.util.IIIFUtils; +import org.dspace.app.iiif.model.generator.AnnotationGenerator; +import org.dspace.app.iiif.model.generator.CanvasGenerator; +import org.dspace.app.iiif.model.generator.ContentAsTextGenerator; +import org.dspace.app.iiif.model.generator.ManifestGenerator; +import org.dspace.app.iiif.model.generator.SearchResultGenerator; +import org.dspace.app.iiif.service.utils.IIIFUtils; import org.dspace.discovery.SolrSearchCore; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamBytesIIIFVirtualMetadata.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamBytesIIIFVirtualMetadata.java similarity index 95% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamBytesIIIFVirtualMetadata.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamBytesIIIFVirtualMetadata.java index 23b7b9ffd5..e042ef3e6b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamBytesIIIFVirtualMetadata.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamBytesIIIFVirtualMetadata.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; +package org.dspace.app.iiif.service.utils; import java.util.Collections; import java.util.List; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamChecksumIIIFVirtualMetadata.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamChecksumIIIFVirtualMetadata.java similarity index 95% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamChecksumIIIFVirtualMetadata.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamChecksumIIIFVirtualMetadata.java index f933d386f2..d7920bdc4b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamChecksumIIIFVirtualMetadata.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamChecksumIIIFVirtualMetadata.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; +package org.dspace.app.iiif.service.utils; import java.util.Collections; import java.util.List; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamFormatIIIFVirtualMetadata.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamFormatIIIFVirtualMetadata.java similarity index 95% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamFormatIIIFVirtualMetadata.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamFormatIIIFVirtualMetadata.java index 4ae1f6f0ff..c4caf0c54e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamFormatIIIFVirtualMetadata.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamFormatIIIFVirtualMetadata.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; +package org.dspace.app.iiif.service.utils; import java.sql.SQLException; import java.util.Collections; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamIIIFVirtualMetadata.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamIIIFVirtualMetadata.java similarity index 93% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamIIIFVirtualMetadata.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamIIIFVirtualMetadata.java index 758ab916c0..46fee60c87 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamIIIFVirtualMetadata.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamIIIFVirtualMetadata.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; +package org.dspace.app.iiif.service.utils; import java.util.List; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamMimetypeIIIFVirtualMetadata.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamMimetypeIIIFVirtualMetadata.java similarity index 95% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamMimetypeIIIFVirtualMetadata.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamMimetypeIIIFVirtualMetadata.java index d147570660..b49b29743f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/BitstreamMimetypeIIIFVirtualMetadata.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/BitstreamMimetypeIIIFVirtualMetadata.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; +package org.dspace.app.iiif.service.utils; import java.sql.SQLException; import java.util.Collections; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java similarity index 99% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java index 0123784d92..11e82a3f2f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/IIIFUtils.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; +package org.dspace.app.iiif.service.utils; import java.sql.SQLException; import java.util.ArrayList; @@ -19,7 +19,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.iiif.model.ObjectMapperFactory; +import org.dspace.app.iiif.model.ObjectMapperFactory; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; import org.dspace.content.Bundle; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/ImageProfileUtil.java similarity index 86% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/ImageProfileUtil.java index c1aeb2555c..376e307652 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ImageProfileUtil.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/ImageProfileUtil.java @@ -5,9 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; +package org.dspace.app.iiif.service.utils; -import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.dspace.app.iiif.model.generator.ProfileGenerator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/ThumbProfileUtil.java similarity index 89% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java rename to dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/ThumbProfileUtil.java index 58e5dd18b3..3652ab326f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/service/util/ThumbProfileUtil.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/ThumbProfileUtil.java @@ -5,9 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.service.util; +package org.dspace.app.iiif.service.utils; -import org.dspace.app.rest.iiif.model.generator.ProfileGenerator; +import org.dspace.app.iiif.model.generator.ProfileGenerator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 27844682bf..d50462c6c8 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -364,6 +364,11 @@ dspace-services + + org.dspace + dspace-iiif + + @@ -493,38 +498,6 @@ ehcache 3.4.0 - - - de.digitalcollections.iiif - iiif-apis - 0.3.9 - - - org.javassist - javassist - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - org.springframework.security - spring-security-core - - - org.dmfs - iterators - - - com.fasterxml.jackson.module - jackson-module-parameter-names - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - org.apache.solr solr-cell 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 4ee6570483..e0ea0cccb0 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 @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; import org.springframework.hateoas.server.LinkRelationProvider; @@ -56,6 +57,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; */ @SpringBootApplication @EnableScheduling +@EnableCaching public class Application extends SpringBootServletInitializer { private static final Logger log = LoggerFactory.getLogger(Application.class); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheLogger.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CacheLogger.java similarity index 87% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheLogger.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CacheLogger.java index c3cd9c188d..18b26b3ceb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheLogger.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CacheLogger.java @@ -5,7 +5,8 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest.iiif.cache; + +package org.dspace.app.rest.cache; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -18,7 +19,7 @@ public class CacheLogger implements CacheEventListener { @Override public void onEvent(CacheEvent cacheEvent) { log.info("Cache Event Type: {} | Manifest Key: {} ", - cacheEvent.getType(), cacheEvent.getKey()); + cacheEvent.getType(), cacheEvent.getKey()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java deleted file mode 100644 index 9134a65432..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/iiif/cache/CacheConfig.java +++ /dev/null @@ -1,24 +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.iiif.cache; - -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Configuration; - -/** - * Enables Spring cache support. The configuration file is defined in - * application properties. - *

    spring.cache.jcache.config=classpath:iiif/cache/ehcache.xml

    - * TODO: Before the cache is used in production there must be a way to - * evict from the cache whenever a dspace item, bundle or bitstream is changed. - */ -@Configuration -@EnableCaching -public class CacheConfig { - // Spring boot cache configuration. -} 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 f180124d15..7170072e13 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 @@ -25,7 +25,7 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; @Configuration @EnableSpringDataWebSupport @ComponentScan( {"org.dspace.app.rest.converter", "org.dspace.app.rest.repository", "org.dspace.app.rest.utils", - "org.dspace.app.configuration", "org.dspace.iiif"}) + "org.dspace.app.configuration", "org.dspace.iiif", "org.dspace.app.iiif"}) public class ApplicationConfig { // Allowed CORS origins ("Access-Control-Allow-Origin" header) // Can be overridden in DSpace configuration diff --git a/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml index 13f9b398b4..cedbff2d46 100644 --- a/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml +++ b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml @@ -16,7 +16,7 @@ - org.dspace.app.rest.iiif.cache.CacheLogger + org.dspace.app.rest.cache.CacheLogger ASYNCHRONOUS UNORDERED CREATED diff --git a/dspace/pom.xml b/dspace/pom.xml index e10f455b26..0a26771dcc 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -204,6 +204,11 @@ dspace-rdf compile
    + + org.dspace + dspace-iiif + compile + org.dspace dspace-services diff --git a/pom.xml b/pom.xml index db14434e1c..af3f0075cf 100644 --- a/pom.xml +++ b/pom.xml @@ -823,6 +823,21 @@ + + + dspace-iiif + + + dspace-iiif/pom.xml + + + + dspace-iiif + + + @@ -1043,6 +1058,11 @@ dspace-rdf 7.1-SNAPSHOT + + org.dspace + dspace-iiif + 7.1-SNAPSHOT + org.dspace dspace-server-webapp From 47a97b2b21c408f5a94a2a3bebe56974351ce1df Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Thu, 14 Oct 2021 10:40:00 +0200 Subject: [PATCH 0410/1254] find all top communities performance --- .../repository/CommunityRestRepository.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index e6498eb1bf..927b6a0b98 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -192,14 +192,26 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository findAllTop(Pageable pageable) { try { - List communities = cs.findAllTop(obtainContext()); - return converter.toRestPage(communities, pageable, utils.obtainProjection()); - } catch (SQLException e) { + Context context = obtainContext(); + List topLevelCommunities = new LinkedList(); + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setQuery("*:*"); + discoverQuery.setDSpaceObjectFilter(IndexableCommunity.TYPE); + discoverQuery.addFilterQueries("-location.parent:*"); + discoverQuery.setStart(Math.toIntExact(pageable.getOffset())); + discoverQuery.setSortField("dc.title_sort", DiscoverQuery.SORT_ORDER.asc); + discoverQuery.setMaxResults(pageable.getPageSize()); + DiscoverResult resp = searchService.search(context, discoverQuery); + long tot = resp.getTotalSearchResults(); + for (IndexableObject solrCommunities : resp.getIndexableObjects()) { + Community c = ((IndexableCommunity) solrCommunities).getIndexedObject(); + topLevelCommunities.add(c); + } + return converter.toRestPage(topLevelCommunities, pageable, tot, utils.obtainProjection()); + } catch (SearchServiceException e) { throw new RuntimeException(e.getMessage(), e); } } From 67ed399c7b5d1f97b37c4435c51d821280e2c50e Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 14 Oct 2021 11:39:28 +0200 Subject: [PATCH 0411/1254] added test to verify IllegalStateException --- .../content/WorkspaceItemServiceImpl.java | 31 ++++++++-------- .../rest/WorkspaceItemRestRepositoryIT.java | 37 +++++++++++++++++++ 2 files changed, 53 insertions(+), 15 deletions(-) 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 ea5edb7601..d891dcf638 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -12,6 +12,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -121,18 +122,20 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { authorizeService .addPolicy(context, item, Constants.DELETE, item.getSubmitter(), ResourcePolicy.TYPE_SUBMISSION); + // Copy template if appropriate + Item templateItem = collection.getTemplateItem(); Optional colEntityType = getDSpaceEntityType(collection); - Optional itemEntityType = getDSpaceEntityType(item); + Optional templateItemEntityType = getDSpaceEntityType(templateItem); - if (colEntityType.isPresent() && itemEntityType.isPresent() && - !StringUtils.equals(colEntityType.get().getValue(), itemEntityType.get().getValue())) { - throw new IllegalStateException("It is not possible to deposite item with entity type : " + - itemEntityType.get().getValue() + " into the collection with different type : " + - colEntityType.get().getValue()); + if (colEntityType.isPresent() && templateItemEntityType.isPresent() && + !StringUtils.equals(colEntityType.get().getValue(), templateItemEntityType.get().getValue())) { + throw new IllegalStateException("The template item has entity type : (" + + templateItemEntityType.get().getValue() + ") different than collection entity type : " + + colEntityType.get().getValue()); } - if (colEntityType.isPresent() && itemEntityType.isEmpty()) { + if (colEntityType.isPresent() && templateItemEntityType.isEmpty()) { MetadataValue original = colEntityType.get(); MetadataField metadataField = original.getMetadataField(); MetadataSchema metadataSchema = metadataField.getMetadataSchema(); @@ -140,9 +143,6 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { metadataField.getQualifier(), original.getLanguage(), original.getValue()); } - // Copy template if appropriate - Item templateItem = collection.getTemplateItem(); - if (template && (templateItem != null)) { List md = itemService.getMetadata(templateItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); @@ -170,11 +170,12 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { } private Optional getDSpaceEntityType(DSpaceObject dSpaceObject) { - return dSpaceObject.getMetadata() - .stream() - .filter(x -> x.getMetadataField().toString('.') - .equalsIgnoreCase("dspace.entity.type")) - .findFirst(); + return Objects.nonNull(dSpaceObject) ? dSpaceObject.getMetadata() + .stream() + .filter(x -> x.getMetadataField().toString('.') + .equalsIgnoreCase("dspace.entity.type")) + .findFirst() + : Optional.empty(); } @Override 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 a9e4599c2b..54d808d94b 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 @@ -67,6 +67,7 @@ import org.dspace.content.Item; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.ItemService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -85,6 +86,8 @@ import org.springframework.test.web.servlet.MvcResult; */ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private ItemService itemService; @Autowired private ConfigurationService configurationService; @@ -5297,4 +5300,38 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration } + @Test + public void invalidCollectionConfigurationPreventItemCreationTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .withTemplateItem() + .withEntityType("Person") + .build(); + + Item templateItem = col.getTemplateItem(); + itemService.addMetadata(context, templateItem, "dspace", "entity", "type", null, "Publication"); + + String authToken = getAuthToken(eperson.getEmail(), password); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + MockMultipartFile pdfFile = new MockMultipartFile("file", "/local/path/myfile.pdf", "application/pdf", pdf); + + context.restoreAuthSystemState(); + + try { + getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + .file(pdfFile)) + .andExpect(status().is(500)); + } finally { + pdf.close(); + } + } + } From 3d3608cf48f6d3589b0aee74c017f65b9565794e Mon Sep 17 00:00:00 2001 From: Corrado Lombardi Date: Thu, 14 Oct 2021 12:59:57 +0200 Subject: [PATCH 0412/1254] [CST-4507] removed commented code --- .../java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java | 1 - 1 file changed, 1 deletion(-) 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 9a2ad4d817..cbd32d7d69 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 @@ -1826,7 +1826,6 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration // verify that it works for administators inspecting other users - by assuming login getClient(adminToken).perform(baseFeatureRequest.get() -// .param("feature", trueForLoggedUsers.getName()) .header("X-On-Behalf-Of", eperson.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", is(4))) From d6a9bca5783c689f04de4622e38582fd78950e1c Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Mon, 11 Oct 2021 17:59:46 +0200 Subject: [PATCH 0413/1254] 84100: Admin only files rights issue --- .../org/dspace/content/ItemServiceImpl.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 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 0ce132d77b..2ed925decd 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1045,12 +1045,12 @@ prevent the generation of resource policy entry values with null dspace_object a * to perform a particular action. */ protected void addDefaultPoliciesNotInPlace(Context context, DSpaceObject dso, - List defaultCollectionPolicies) - throws SQLException, AuthorizeException { + List defaultCollectionPolicies) throws SQLException, AuthorizeException { + for (ResourcePolicy defaultPolicy : defaultCollectionPolicies) { if (!authorizeService .isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ, - defaultPolicy.getID())) { + defaultPolicy.getID()) && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso)) { ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy); newPolicy.setdSpaceObject(dso); newPolicy.setAction(Constants.READ); @@ -1060,6 +1060,25 @@ prevent the generation of resource policy entry values with null dspace_object a } } + /** + * Check whether or not there is already an RP on the given dso, which has actionId={@link Constants.READ} and + * resourceTypeId={@link ResourcePolicy.TYPE_CUSTOM} + * + * @param context DSpace context + * @param dso DSpace object to check for custom read RP + * @return True if there is no RP on the item with custom read RP, otherwise false + * @throws SQLException If something goes wrong retrieving the RP on the DSO + */ + private boolean isNotAlreadyACustomRPOfThisTypeOnDSO(Context context, DSpaceObject dso) throws SQLException { + List readRPs = resourcePolicyService.find(context, dso, Constants.READ); + for (ResourcePolicy readRP : readRPs) { + if (readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { + return false; + } + } + return true; + } + /** * Returns an iterator of Items possessing the passed metadata field, or only * those matching the passed value, if value is not Item.ANY From a0e92a5f18fe64c4eceb7d52e6a15412513d27ef Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 13 Oct 2021 11:40:18 +0200 Subject: [PATCH 0414/1254] 84100: Admin only files rights issue IT --- .../org/dspace/content/ItemServiceImpl.java | 2 +- .../rest/WorkspaceItemRestRepositoryIT.java | 240 ++++++++++++++++++ .../rest/matcher/ResourcePolicyMatcher.java | 26 ++ 3 files changed, 267 insertions(+), 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 2ed925decd..6656028e7b 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1072,7 +1072,7 @@ prevent the generation of resource policy entry values with null dspace_object a private boolean isNotAlreadyACustomRPOfThisTypeOnDSO(Context context, DSpaceObject dso) throws SQLException { List readRPs = resourcePolicyService.find(context, dso, Constants.READ); for (ResourcePolicy readRP : readRPs) { - if (readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { + if (readRP.getRpType() != null && readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { return false; } } 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 ffaf6f2da7..c5fa04841c 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 @@ -43,12 +43,16 @@ import org.apache.commons.lang3.time.DateUtils; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.matcher.ResourcePolicyMatcher; import org.dspace.app.rest.matcher.WorkspaceItemMatcher; 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.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -67,9 +71,11 @@ import org.dspace.content.Item; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; +import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Before; @@ -88,6 +94,10 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Autowired private ConfigurationService configurationService; + private GroupService groupService; + + private ResourcePolicyService resourcePolicyService; + private Group embargoedGroups; private Group embargoedGroup1; private Group embargoedGroup2; @@ -98,6 +108,8 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration public void setUp() throws Exception { super.setUp(); context.turnOffAuthorisationSystem(); + this.groupService = EPersonServiceFactory.getInstance().getGroupService(); + this.resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); embargoedGroups = GroupBuilder.createGroup(context) .withName("Embargoed Groups") @@ -5299,4 +5311,232 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration } + @Test + public void patchUploadAddAdminRPInstallAndVerifyOnlyAdminCanView() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withSubmitter(eperson) + .withTitle( + "Test Item patchUploadAddAdminRPInstallAndVerifyOnlyAdminCanView") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + // auth + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + // prepare patch body + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "administrator"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken) + .perform( + patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + // verify that bitstream of workspace item has this admin RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken) + .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); + + // verify that bitstream of workspace item still has this admin RP and no Anon READ inherited policy + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.not(Matchers.hasItems( + ResourcePolicyMatcher + .matchResourcePolicyProperties(anonGroup, null, bitstream, null, Constants.READ, + null) + )))); + + // Bitstream should NOT be accessible to anon or eperson user, only to admin + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isUnauthorized()); + getClient(epersonToken).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isForbidden()); + getClient(adminToken).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + + @Test + public void patchUploadAddOpenAccessRPInstallAndVerifyAnonCanView() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection).withSubmitter(eperson) + .withTitle("Test Item patchUploadAddOpenAccessRPInstallAndVerifyOnlyAdminCanView") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + // auth + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + // prepare patch body + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "openaccess"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken) + .perform( + patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + + // verify that bitstream of workspace item has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken) + .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // verify that bitstream of workspace item still has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess") + ))); + + // Bitstream should be accessible to anon + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + + @Test + public void patchUploadAddAdminThenOpenAccessRPInstallAndVerifyAnonCanView() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection).withSubmitter(eperson) + .withTitle("Test Item patchUploadAddOpenAccessRPInstallAndVerifyOnlyAdminCanView") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + // auth + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + // prepare patch body + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "administrator"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken) + .perform( + patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + // verify that bitstream of workspace item has this admin RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))); + + // prepare patch body + Map accessCondition2 = new HashMap<>(); + accessCondition2.put("name", "openaccess"); + List ops2 = new ArrayList<>(); + ops2.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition2)); + String patchBody2 = getPatchContent(ops2); + + // submit patch and verify response + getClient(epersonToken) + .perform( + patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody2) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON) + ) + .andExpect(status().isOk()); + + // verify that bitstream of workspace item has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken) + .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // Bitstream should be accessible to anon + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java index 88d247d091..b0423b5455 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java @@ -14,8 +14,13 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import javax.annotation.Nullable; + import org.dspace.authorize.ResourcePolicy; +import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.hamcrest.Matcher; /** @@ -29,6 +34,27 @@ public class ResourcePolicyMatcher { private ResourcePolicyMatcher() { } + public static Matcher matchResourcePolicyProperties(@Nullable Group group, + @Nullable EPerson eperson, DSpaceObject dso, @Nullable String rpType, int action, @Nullable String name) { + return allOf( + hasJsonPath("$.name", is(name)), + hasJsonPath("$.action", is(Constants.actionText[action])), + rpType != null ? + hasJsonPath("$.policyType", is(rpType)) : + hasNoJsonPath("$.policyType"), + hasJsonPath("$.type", is("resourcepolicy")), + hasJsonPath("$._embedded.resource.id", is(dso.getID().toString())), + eperson != null ? + hasJsonPath("$._embedded.eperson.id", + is(eperson.getID().toString())) : + hasJsonPath("$._embedded.eperson", nullValue()), + group != null ? + hasJsonPath("$._embedded.group.id", + is(group.getID().toString())) : + hasJsonPath("$._embedded.group", nullValue()) + ); + } + public static Matcher matchResourcePolicy(ResourcePolicy resourcePolicy) { return allOf(hasJsonPath("$.id", is(resourcePolicy.getID())), hasJsonPath("$.name", is(resourcePolicy.getRpName())), From c4ffbe10be9a3424de9d61c79494e8233d56ae46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Thu, 14 Oct 2021 12:52:02 +0100 Subject: [PATCH 0415/1254] remove openaire API token placeholders --- .../data/dspaceFolder/config/spring/api/external-openaire.xml | 4 ++-- .../data/dspaceFolder/config/spring/api/external-openaire.xml | 4 ++-- dspace/config/spring/api/external-openaire.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index d3a7223491..d2d5be9c39 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -7,8 +7,8 @@ - - + +
    diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index 7698b50f8e..264275c1c6 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -13,9 +13,9 @@ + value="${openaire.token.clientId}" /> + value="${openaire.token.clientSecret}" /> - - + + From 9a8ba86b5eab4f2c28620807731cdf65f04ca256 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 14 Oct 2021 12:52:10 -0700 Subject: [PATCH 0416/1254] Using dspace-api ContextUtil with iiif controller. --- .../java/org/dspace/web}/ContextUtil.java | 84 +++++++++++++++---- .../org/dspace/app/iiif/IIIFController.java | 2 +- 2 files changed, 69 insertions(+), 17 deletions(-) rename {dspace-iiif/src/main/java/org/dspace/app/iiif/dspace => dspace-api/src/main/java/org/dspace/web}/ContextUtil.java (74%) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/dspace/ContextUtil.java b/dspace-api/src/main/java/org/dspace/web/ContextUtil.java similarity index 74% rename from dspace-iiif/src/main/java/org/dspace/app/iiif/dspace/ContextUtil.java rename to dspace-api/src/main/java/org/dspace/web/ContextUtil.java index 3f90a98068..4bdf26c053 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/dspace/ContextUtil.java +++ b/dspace-api/src/main/java/org/dspace/web/ContextUtil.java @@ -5,21 +5,36 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.iiif.dspace; +package org.dspace.web; import java.sql.SQLException; import java.util.Enumeration; import java.util.Locale; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; import org.dspace.core.Context; import org.dspace.core.I18nUtil; import org.dspace.services.RequestService; import org.dspace.services.model.Request; import org.dspace.utils.DSpace; +/** + * Miscellaneous UI utility methods methods for managing DSpace context. + * + * This class was "adapted" from the class of the same name in old XMLUI. + * + * @author Tim Donohue + */ public class ContextUtil { + /** + * The log4j logger + */ + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ContextUtil.class); + /** * Where the context is stored on an HTTP Request object */ @@ -28,23 +43,22 @@ public class ContextUtil { /** * Default constructor */ - private ContextUtil() { - } + private ContextUtil() { } /** - * Shortcut for {@link #obtainContext(Request)} using the {@link RequestService} - * to retrieve the current thread request + * Inspection method to check if a DSpace context has been created for this request. * - * @return the DSpace Context associated with the current thread-bound request + * @param request the servlet request object + * @return True if a context has previously been created, false otherwise. */ - public static Context obtainCurrentRequestContext() { - Context context = null; - RequestService requestService = new DSpace().getRequestService(); - Request currentRequest = requestService.getCurrentRequest(); - if (currentRequest != null) { - context = ContextUtil.obtainContext(currentRequest.getHttpServletRequest()); + public static boolean isContextAvailable(ServletRequest request) { + Object object = request.getAttribute(DSPACE_CONTEXT); + + if (object instanceof Context) { + return true; + } else { + return false; } - return context; } /** @@ -61,7 +75,7 @@ public class ContextUtil { try { context = ContextUtil.initializeContext(); } catch (SQLException e) { - //log.error("Unable to initialize context", e); + log.error("Unable to initialize context", e); return null; } @@ -76,6 +90,22 @@ public class ContextUtil { return context; } + /** + * Shortcut for {@link #obtainContext(Request)} using the {@link RequestService} + * to retrieve the current thread request + * + * @return the DSpace Context associated with the current thread-bound request + */ + public static Context obtainCurrentRequestContext() { + Context context = null; + RequestService requestService = new DSpace().getRequestService(); + Request currentRequest = requestService.getCurrentRequest(); + if (currentRequest != null) { + context = ContextUtil.obtainContext(currentRequest.getHttpServletRequest()); + } + return context; + } + private static Locale getLocale(Context context, HttpServletRequest request) { Locale userLocale = null; Locale supportedLocale = null; @@ -107,7 +137,6 @@ public class ContextUtil { return supportedLocale; } - /** * Initialize a new Context object * @@ -117,7 +146,6 @@ public class ContextUtil { private static Context initializeContext() throws SQLException { // Create a new Context Context context = new Context(); - // Set the session ID /**context.setExtraLogInfo("session_id=" + request.getSession().getId()); @@ -155,4 +183,28 @@ public class ContextUtil { return context; } + /** + * Check if a context exists for this request, if so complete the context. + * + * @param request The request object + */ + public static void completeContext(ServletRequest request) throws ServletException { + Context context = (Context) request.getAttribute(DSPACE_CONTEXT); + + if (context != null && context.isValid()) { + try { + context.complete(); + } catch (SQLException e) { + throw new ServletException(e); + } + } + } + + public static void abortContext(ServletRequest request) { + Context context = (Context) request.getAttribute(DSPACE_CONTEXT); + + if (context != null && context.isValid()) { + context.abort(); + } + } } diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java index b0e4130f10..d48d10d44b 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java @@ -9,10 +9,10 @@ package org.dspace.app.iiif; import java.util.UUID; -import org.dspace.app.iiif.dspace.ContextUtil; import org.dspace.core.Context; import org.dspace.services.RequestService; import org.dspace.utils.DSpace; +import org.dspace.web.ContextUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; From 138ae70a32f9a4b9cc5bd8d08e6ab71d579a7b22 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 14 Oct 2021 12:53:39 -0700 Subject: [PATCH 0417/1254] Docs update. --- .../dspace/iiif/IIIFCacheEventConsumer.java | 3 +- .../app/rest/iiif/IIIFControllerIT.java | 29 +++++++------------ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java index e721ae8eb0..835664a820 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java @@ -45,7 +45,8 @@ public class IIIFCacheEventConsumer implements Consumer { @Override public void consume(Context ctx, Event event) throws Exception { - // The service null when the web application context is not provided (i.e. command line operations). + // The service is null when the web application context is not available (i.e. during command line operations). + // Return to avoid event processing and NPE. if (cacheEvictService == null) { return; } 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 3915a99cf6..3cf529ec09 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 @@ -1034,26 +1034,17 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { * Our troubles are with the dispatcher and the way that flyway initializes the test environment. * If we add "iiif" to the list of default dispatchers in dspace.cfg, the initialization * triggers a call to the IIIFCacheEventConsumer before the web ApplicationContext has been injected. The - * CacheEvictService is not set in the consumer, and all subsequent calls to the consumer fail. If - * this root problem is solved and "iiif" can be added to the default dispatcher, then I think this test would - * succeed as written. (If possible this might be the best solution.) + * CacheEvictService is not set in the consumer, and all subsequent calls to the consumer fail. * - * The test succeeds with the following code modifications. + * The test succeeds with the following: * - * 1. Make sure the default dispatcher in dspace.cfg excludes the iiif consumer. - * 2. Add a new dispatcher definition to the test local.cfg that includes the iiif consumer. - * 3. Modify the web application's ContextUtil.initializeContext to add the new dispatcher to the context - * -- context.setDispatcher(iiif-dispatcher). - * - * The third change is obviously wrong, but it can be done temporarily to verify that the item is - * evicted from the cache when a patch is applied. Perhaps there's a reasonable way to set the test context - * in the web application that I don't know about. - * - * The alternative is to set the dispatcher here inside the test. Then instead of using the - * patch request, update the test Item using the dspace-api. The cache service is called and the cache - * is cleared. The manifest endpoint generates a new manifest as expected. But, the new response - * contains the old title. That doesn't seem right to me so there may be a way to make this approach work. + * 1. Verify that the iiif consumer is included in the default dispatcher in dspace.cfg. + * 2. In RegistryUpdater, MetadataImporter, and GroupServiceInitializer set context to use a dispatcher + * without the iiif consumer. This prevents premature initialization during tests. (I tried this using the + * existing "noindex" dispatcher.) * + * The consumer is working in production so the issue here seems to appear only during tests and + * database migrations. */ // String patchRequestBody = // "[{\"op\": \"replace\",\"path\": \"/metadata/dc.title/0/value\",\"value\": \"Public item 1 (revised)\"}]"; @@ -1121,8 +1112,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { // getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) // .andExpect(status().isOk()) // .andExpect(jsonPath("$.metadata[0].value", is("Public item 1 (revised)"))); - - +// +// } } From f19d01cb5c19a42a372650579dfb50c6595922c4 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 14 Oct 2021 12:54:13 -0700 Subject: [PATCH 0418/1254] Added iiif consumer to default dispatcher. --- dspace/config/dspace.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 9f68e10578..1207c9ecff 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -696,7 +696,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. -event.dispatcher.default.consumers = versioning, discovery, eperson +event.dispatcher.default.consumers = versioning, discovery, eperson, iiif # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher From c30721b55a129826cdb235a68c1904bad60ece63 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 14 Oct 2021 13:58:28 -0700 Subject: [PATCH 0419/1254] Adding rendering to manifest instead of sequence. --- .../model/generator/CanvasItemsGenerator.java | 14 ------- .../model/generator/ManifestGenerator.java | 13 +++++++ .../app/iiif/service/ManifestService.java | 37 +++++++++++++++++- .../app/iiif/service/SequenceService.java | 39 ------------------- .../app/rest/iiif/IIIFControllerIT.java | 6 +-- 5 files changed, 52 insertions(+), 57 deletions(-) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java index d7b6bdf0ca..08ac918bb6 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java @@ -10,7 +10,6 @@ package org.dspace.app.iiif.model.generator; import java.util.ArrayList; import java.util.List; -import de.digitalcollections.iiif.model.OtherContent; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Resource; import de.digitalcollections.iiif.model.sharedcanvas.Sequence; @@ -32,10 +31,8 @@ import org.springframework.web.context.annotation.RequestScope; public class CanvasItemsGenerator implements IIIFResource { private String identifier; - private final List renderings = new ArrayList<>(); private final List canvas = new ArrayList<>(); - /** * Sets the required identifier property. * @param identifier URI string @@ -44,14 +41,6 @@ public class CanvasItemsGenerator implements IIIFResource { this.identifier = identifier; } - /** - * Adds a rendering annotation to the Sequence. The rendering is a link to an external resource intended - * for display or download by a human user. This is typically going to be a PDF file. - * @param otherContent generator for the resource - */ - public void addRendering(ExternalLinksGenerator otherContent) { - this.renderings.add((OtherContent) otherContent.generateResource()); - } /** * Adds a single {@code Canvas} to the sequence. @@ -66,9 +55,6 @@ public class CanvasItemsGenerator implements IIIFResource { @Override public Resource generateResource() { Sequence items = new Sequence(identifier); - for (OtherContent r : renderings) { - items.addRendering(r); - } items.setCanvases(canvas); return items; } diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java index 4fa1d00ea8..70f56322dd 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java @@ -54,6 +54,7 @@ public class ManifestGenerator implements IIIFResource { private OtherContent related; private ImageContent thumbnail; private ContentSearchService searchService; + private List renderings = new ArrayList<>(); private final List license = new ArrayList<>(); private final List metadata = new ArrayList<>(); private final List ranges = new ArrayList<>(); @@ -166,6 +167,15 @@ public class ManifestGenerator implements IIIFResource { ranges.add((Range) rangeGenerator.generateResource()); } + /** + * Adds a rendering annotation to the Sequence. The rendering is a link to an external resource intended + * for display or download by a human user. This is typically going to be a PDF file. + * @param otherContent generator for the resource + */ + public void addRendering(ExternalLinksGenerator otherContent) { + this.renderings.add((OtherContent) otherContent.generateResource()); + } + @Override public Resource generateResource() { @@ -178,6 +188,9 @@ public class ManifestGenerator implements IIIFResource { } else { manifest = new Manifest(identifier); } + if (renderings.size() > 0) { + manifest.setRenderings(renderings); + } if (logo != null) { List logos = new ArrayList<>(); logos.add(logo); 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 7dcb474a75..ffe533e7bd 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 @@ -17,6 +17,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.iiif.model.generator.CanvasGenerator; import org.dspace.app.iiif.model.generator.ContentSearchGenerator; +import org.dspace.app.iiif.model.generator.ExternalLinksGenerator; import org.dspace.app.iiif.model.generator.ImageContentGenerator; import org.dspace.app.iiif.model.generator.ManifestGenerator; import org.dspace.app.iiif.model.generator.RangeGenerator; @@ -124,6 +125,7 @@ public class ManifestService extends AbstractResourceService { addRanges(context, item, manifestId); manifestGenerator.addSequence( sequenceService.getSequence(item, context)); + addRendering(item, context); addSeeAlso(item); } @@ -140,7 +142,6 @@ public class ManifestService extends AbstractResourceService { RangeGenerator root = new RangeGenerator(rangeService); root.setLabel(I18nUtil.getMessage("iiif.toc.root-label")); root.setIdentifier(manifestId + "/range/r0"); -// manifestGenerator.addRange(root); Map tocRanges = new LinkedHashMap(); for (Bundle bnd : bundles) { @@ -319,4 +320,38 @@ public class ManifestService extends AbstractResourceService { } } + /** + * This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds + * it to the Sequence if found. + * + * @param item DSpace Item + * @param context DSpace context + */ + private void addRendering(Item item, Context context) { + List bundles = utils.getIIIFBundles(item); + for (Bundle bundle : bundles) { + List bitstreams = bundle.getBitstreams(); + for (Bitstream bitstream : bitstreams) { + String mimeType = null; + try { + mimeType = bitstream.getFormat(context).getMIMEType(); + } catch (SQLException e) { + e.printStackTrace(); + } + // If the bundle contains a PDF, assume that it represents the + // item and add to rendering. Ignore other mime-types. Other options + // might be using the primary bitstream or relying on a bitstream metadata + // field, e.g. iiif.rendering + if (mimeType != null && mimeType.contentEquals("application/pdf")) { + String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; + manifestGenerator.addRendering( + new ExternalLinksGenerator(id) + .setLabel(utils.getIIIFLabel(bitstream, bitstream.getName())) + .setFormat(mimeType) + ); + } + } + } + } + } diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java index a37088fc5e..cad81c382b 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java @@ -7,13 +7,9 @@ */ package org.dspace.app.iiif.service; -import java.sql.SQLException; -import java.util.List; - import org.apache.logging.log4j.Logger; import org.dspace.app.iiif.model.generator.CanvasGenerator; import org.dspace.app.iiif.model.generator.CanvasItemsGenerator; -import org.dspace.app.iiif.model.generator.ExternalLinksGenerator; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -62,8 +58,6 @@ public class SequenceService extends AbstractResourceService { public CanvasItemsGenerator getSequence(Item item, Context context) { sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); - addRendering(item, context); - return sequenceGenerator; } @@ -86,38 +80,5 @@ public class SequenceService extends AbstractResourceService { return canvasGenerator; } - /** - * This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds - * it to the Sequence if found. - * - * @param item DSpace Item - * @param context DSpace context - */ - private void addRendering(Item item, Context context) { - List bundles = utils.getIIIFBundles(item); - for (Bundle bundle : bundles) { - List bitstreams = bundle.getBitstreams(); - for (Bitstream bitstream : bitstreams) { - String mimeType = null; - try { - mimeType = bitstream.getFormat(context).getMIMEType(); - } catch (SQLException e) { - e.printStackTrace(); - } - // If the bundle contains a PDF, assume that it represents the - // item and add to rendering. Ignore other mime-types. Other options - // might be using the primary bitstream or relying on a bitstream metadata - // field, e.g. iiif.rendering - if (mimeType != null && mimeType.contentEquals("application/pdf")) { - String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content"; - sequenceGenerator.addRendering( - new ExternalLinksGenerator(id) - .setLabel(utils.getIIIFLabel(bitstream, bitstream.getName())) - .setFormat(mimeType) - ); - } - } - } - } } 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 3cf529ec09..a11939fb61 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 @@ -824,10 +824,10 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .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].rendering.@id", + .andExpect(jsonPath("$.rendering.@id", Matchers.endsWith(pdf.getID().toString() + "/content"))) - .andExpect(jsonPath("$.sequences[0].rendering.label", is("Bitstream3.pdf"))) - .andExpect(jsonPath("$.sequences[0].rendering.format", is("application/pdf"))) + .andExpect(jsonPath("$.rendering.label", is("Bitstream3.pdf"))) + .andExpect(jsonPath("$.rendering.format", is("application/pdf"))) .andExpect(jsonPath("$.service").doesNotExist()); } From 8f2335175808ba26d7258edea61c6979ab81f927 Mon Sep 17 00:00:00 2001 From: Joost Date: Fri, 15 Oct 2021 15:43:52 +0200 Subject: [PATCH 0420/1254] [task 84277] ensuring the right exception is expected when multiple items with the same handle get installed --- .../org/dspace/content/InstallItemTest.java | 42 ++++++------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/InstallItemTest.java b/dspace-api/src/test/java/org/dspace/content/InstallItemTest.java index b40e46c180..60c1bc713d 100644 --- a/dspace-api/src/test/java/org/dspace/content/InstallItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/InstallItemTest.java @@ -11,8 +11,6 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import java.io.File; import java.io.FileInputStream; @@ -26,7 +24,6 @@ import java.util.TimeZone; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -38,7 +35,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.test.util.ReflectionTestUtils; /** * Unit Tests for class InstallItem @@ -57,12 +53,6 @@ public class InstallItemTest extends AbstractUnitTest { private Collection collection; private Community owningCommunity; - /** - * Spy of AuthorizeService to use for tests - * (initialized / setup in @Before method) - */ - private AuthorizeService authorizeServiceSpy; - /** * log4j category */ @@ -84,14 +74,6 @@ public class InstallItemTest extends AbstractUnitTest { this.owningCommunity = communityService.create(null, context); this.collection = collectionService.create(context, owningCommunity); context.restoreAuthSystemState(); - - // Initialize our spy of the autowired (global) authorizeService bean. - // This allows us to customize the bean's method return values in tests below - authorizeServiceSpy = spy(authorizeService); - // "Wire" our spy to be used by the current loaded workspaceItemService and collectionService - // (To ensure it uses the spy instead of the real service) - ReflectionTestUtils.setField(workspaceItemService, "authorizeService", authorizeServiceSpy); - ReflectionTestUtils.setField(collectionService, "authorizeService", authorizeServiceSpy); } catch (SQLException | AuthorizeException ex) { log.error("SQL Error in init", ex); fail("SQL Error in init: " + ex.getMessage()); @@ -154,23 +136,23 @@ public class InstallItemTest extends AbstractUnitTest { /** * Test of installItem method (with an invalid handle), of class InstallItem. */ - @Test(expected = AuthorizeException.class) + @Test(expected = IllegalStateException.class) public void testInstallItem_invalidHandle() throws Exception { - // Allow full Admin rights - when(authorizeServiceSpy.isAdmin(context)).thenReturn(true); - // create two items for tests context.turnOffAuthorisationSystem(); - WorkspaceItem is = workspaceItemService.create(context, collection, false); - WorkspaceItem is2 = workspaceItemService.create(context, collection, false); - context.restoreAuthSystemState(); + try { + WorkspaceItem is = workspaceItemService.create(context, collection, false); + WorkspaceItem is2 = workspaceItemService.create(context, collection, false); - //Test assigning the same Handle to two different items - String handle = "123456789/56789"; - installItemService.installItem(context, is, handle); + //Test assigning the same Handle to two different items + String handle = "123456789/56789"; + installItemService.installItem(context, is, handle); - // Assigning the same handle again should throw a RuntimeException - installItemService.installItem(context, is2, handle); + // Assigning the same handle again should throw a RuntimeException + installItemService.installItem(context, is2, handle); + } finally { + context.restoreAuthSystemState(); + } fail("Exception expected"); } From 9f1704f91f95b206acff2b7d99ff76430ecd957b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 15 Oct 2021 09:57:24 -0500 Subject: [PATCH 0421/1254] Ensure any updated Solr configs are copied over to running cores --- docker-compose.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 5d80d95090..64dca2e4ce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,15 +77,22 @@ services: # Keep Solr data directory between reboots - solr_data:/var/solr/data # Initialize all DSpace Solr cores using the mounted local configsets (see above), then start Solr + # * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op + # * Second, copy updated configs from mounted configsets to this core. If it already existed, this updates core + # to the latest configs. If it's a newly created core, this is a no-op. entrypoint: - /bin/bash - '-c' - | init-var-solr precreate-core authority /opt/solr/server/solr/configsets/authority + cp -r -u /opt/solr/server/solr/configsets/authority/* authority precreate-core oai /opt/solr/server/solr/configsets/oai + cp -r -u /opt/solr/server/solr/configsets/oai/* oai precreate-core search /opt/solr/server/solr/configsets/search + cp -r -u /opt/solr/server/solr/configsets/search/* search precreate-core statistics /opt/solr/server/solr/configsets/statistics + cp -r -u /opt/solr/server/solr/configsets/statistics/* statistics exec solr -f volumes: assetstore: From e6cfdf7df6e2f3c7e11b8fe177a794e7e93dc47c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 15 Oct 2021 17:37:19 +0200 Subject: [PATCH 0422/1254] Implement community feedbacks --- .../org/dspace/content/CollectionServiceImpl.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 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 5f1d1e0c43..58085ef0d8 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -17,6 +17,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.MissingResourceException; +import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -995,15 +996,11 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i query.append(")"); discoverQuery.addFilterQueries(query.toString()); } - StringBuilder buildFilter = new StringBuilder(); - if (community != null) { - buildFilter.append("location.comm:").append(community.getID().toString()); + if (Objects.nonNull(community)) { + discoverQuery.addFilterQueries("location.comm:" + community.getID().toString()); } if (StringUtils.isNotBlank(entityType)) { - if (buildFilter.length() > 0) { - buildFilter.append(" AND "); - } - buildFilter.append("search.entitytype:").append(entityType); + discoverQuery.addFilterQueries("search.entitytype:" + entityType); } if (StringUtils.isNotBlank(q)) { StringBuilder buildQuery = new StringBuilder(); @@ -1011,7 +1008,6 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i buildQuery.append(escapedQuery).append(" OR ").append(escapedQuery).append("*"); discoverQuery.setQuery(buildQuery.toString()); } - discoverQuery.addFilterQueries(buildFilter.toString()); DiscoverResult resp = searchService.search(context, discoverQuery); return resp; } From ca82a297c5df7004aa9f9b13f144d375f669e8ce Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 16 Oct 2021 21:52:35 +0200 Subject: [PATCH 0423/1254] Fix consumer to avoid early initialization of the iiif cache evict service --- .../dspace/iiif/IIIFCacheEventConsumer.java | 12 +- .../app/rest/iiif/IIIFControllerIT.java | 144 +++++++----------- 2 files changed, 60 insertions(+), 96 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java index 835664a820..3d39570fc6 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java @@ -28,28 +28,18 @@ public class IIIFCacheEventConsumer implements Consumer { private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFCacheEventConsumer.class); - // Gets the service bean. - private CacheEvictService cacheEvictService; - // When true all entries will be cleared from cache. private boolean clearAll = false; // Collects modified items for individual removal from cache. private final Set toEvictFromManifestCache = new HashSet<>(); - @Override public void initialize() throws Exception { - cacheEvictService = CacheEvictBeanLocator.getCacheEvictService(); } @Override public void consume(Context ctx, Event event) throws Exception { - // The service is null when the web application context is not available (i.e. during command line operations). - // Return to avoid event processing and NPE. - if (cacheEvictService == null) { - return; - } int st = event.getSubjectType(); if (!(st == Constants.BUNDLE || st == Constants.ITEM || st == Constants.BITSTREAM)) { return; @@ -127,6 +117,8 @@ public class IIIFCacheEventConsumer implements Consumer { @Override public void end(Context ctx) throws Exception { + // Gets the service bean. + CacheEvictService cacheEvictService = CacheEvictBeanLocator.getCacheEvictService(); if (cacheEvictService != null) { if (clearAll) { cacheEvictService.evictAllCacheValues(); 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 a11939fb61..0300ce7540 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 @@ -9,11 +9,13 @@ package org.dspace.app.rest.iiif; 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.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; import java.util.UUID; +import javax.ws.rs.core.MediaType; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; @@ -1027,93 +1029,63 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { @Test public void findOneWithCacheEvictionAfterItemUpdate() throws Exception { + String patchRequestBody = + "[{\"op\": \"replace\",\"path\": \"/metadata/dc.title/0/value\",\"value\": \"Public item 1 (revised)\"}]"; - /** - * This test is broken. - * - * Our troubles are with the dispatcher and the way that flyway initializes the test environment. - * If we add "iiif" to the list of default dispatchers in dspace.cfg, the initialization - * triggers a call to the IIIFCacheEventConsumer before the web ApplicationContext has been injected. The - * CacheEvictService is not set in the consumer, and all subsequent calls to the consumer fail. - * - * The test succeeds with the following: - * - * 1. Verify that the iiif consumer is included in the default dispatcher in dspace.cfg. - * 2. In RegistryUpdater, MetadataImporter, and GroupServiceInitializer set context to use a dispatcher - * without the iiif consumer. This prevents premature initialization during tests. (I tried this using the - * existing "noindex" dispatcher.) - * - * The consumer is working in production so the issue here seems to appear only during tests and - * database migrations. - */ -// String patchRequestBody = -// "[{\"op\": \"replace\",\"path\": \"/metadata/dc.title/0/value\",\"value\": \"Public item 1 (revised)\"}]"; -// -// 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(); -// -// String bitstreamContent = "ThisIsSomeDummyText"; -// Bitstream bitstream1 = null; -// Bitstream bitstream2 = null; -// try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { -// bitstream1 = BitstreamBuilder -// .createBitstream(context, publicItem1, is) -// .withName("Bitstream1.jpg") -// .withMimeType("image/jpeg") -// .build(); -// } -// -// try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { -// bitstream2 = BitstreamBuilder -// .createBitstream(context, publicItem1, is) -// .withName("Bitstream2.png") -// .withMimeType("image/png") -// .build(); -// } -// -// context.restoreAuthSystemState(); -// -// // Default canvas size and label. -// getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.metadata[0].label", is("Title"))) -// .andExpect(jsonPath("$.metadata[0].value", is("Public item 1"))); -// -// -// String token = getAuthToken(admin.getEmail(), password); -// -// // The Item update should also remove the manifest from the cache. -// getClient(token).perform(patch("/api/core/items/" + publicItem1.getID()) -// .content(patchRequestBody) -// .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) -// .andExpect(status().isOk()); -// -//// context.turnOffAuthorisationSystem(); -//// List metadataList = publicItem1.getMetadata(); -//// metadataList.get(0).setValue("Public item 1 (revised)"); -//// publicItem1.setMetadata(metadataList); -//// itemService.update(context, publicItem1); -//// context.commit(); -//// context.restoreAuthSystemState(); -// -// // Verify that the updated title is in the manifest. -// getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.metadata[0].value", is("Public item 1 (revised)"))); -// -// + 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(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream2.png") + .withMimeType("image/png") + .build(); + } + + context.restoreAuthSystemState(); + + // Default canvas size and label. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata[0].label", is("Title"))) + .andExpect(jsonPath("$.metadata[0].value", is("Public item 1"))); + + String token = getAuthToken(admin.getEmail(), password); + + // The Item update should also remove the manifest from the cache. + getClient(token).perform(patch("/api/core/items/" + publicItem1.getID()) + .content(patchRequestBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + // Verify that the updated title is in the manifest. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata[0].value", is("Public item 1 (revised)"))); } } From dfead9f29e96c71cd485e32edb9a8ff591aa684c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 16 Oct 2021 22:02:25 +0200 Subject: [PATCH 0424/1254] Add attributions to the main iiif classes --- .../src/main/java/org/dspace/app/iiif/IIIFController.java | 3 +++ .../src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java | 3 +++ .../org/dspace/app/iiif/model/generator/BehaviorGenerator.java | 3 +++ .../org/dspace/app/iiif/model/generator/CanvasGenerator.java | 3 +++ .../dspace/app/iiif/model/generator/CanvasItemsGenerator.java | 3 +++ .../dspace/app/iiif/model/generator/ImageContentGenerator.java | 3 +++ .../dspace/app/iiif/model/generator/ImageServiceGenerator.java | 3 +++ .../org/dspace/app/iiif/model/generator/ManifestGenerator.java | 3 +++ .../app/iiif/model/generator/MetadataEntryGenerator.java | 3 +++ .../org/dspace/app/iiif/model/generator/RangeGenerator.java | 3 +++ .../org/dspace/app/iiif/service/AbstractResourceService.java | 3 +++ .../org/dspace/app/iiif/service/AnnotationListService.java | 3 +++ .../java/org/dspace/app/iiif/service/CanvasLookupService.java | 3 +++ .../main/java/org/dspace/app/iiif/service/CanvasService.java | 3 +++ .../java/org/dspace/app/iiif/service/ImageContentService.java | 3 +++ .../main/java/org/dspace/app/iiif/service/ManifestService.java | 3 +++ .../main/java/org/dspace/app/iiif/service/RangeService.java | 3 +++ .../main/java/org/dspace/app/iiif/service/RelatedService.java | 3 +++ .../main/java/org/dspace/app/iiif/service/SeeAlsoService.java | 3 +++ .../main/java/org/dspace/app/iiif/service/SequenceService.java | 3 +++ 20 files changed, 60 insertions(+) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java index d48d10d44b..a1e265ccfc 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java @@ -23,6 +23,9 @@ import org.springframework.web.bind.annotation.RestController; /** * Controller for IIIF Presentation and Search API. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RestController @RequestMapping("/iiif") diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java index cf0d75c391..7bb723ea65 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java @@ -27,6 +27,9 @@ import org.springframework.stereotype.Service; /** * IIIF Service facade to support IIIF Presentation and Search API requests. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Service public class IIIFServiceFacade { diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/BehaviorGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/BehaviorGenerator.java index 3d3fccd1fa..75b5b67d48 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/BehaviorGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/BehaviorGenerator.java @@ -14,6 +14,9 @@ import de.digitalcollections.iiif.model.enums.ViewingHint; * displaying the resource. * * With IIIF Presentation API 3.0 the viewingHint property is renamed to "behavior". + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ public class BehaviorGenerator implements IIIFValue { diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java index 096abfea58..f064a1b974 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasGenerator.java @@ -18,6 +18,9 @@ import de.digitalcollections.iiif.model.sharedcanvas.Resource; /** * This generator wraps the domain model for a single {@code Canvas}. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ public class CanvasGenerator implements IIIFResource { diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java index 08ac918bb6..f9dfaa2624 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/CanvasItemsGenerator.java @@ -25,6 +25,9 @@ import org.springframework.web.context.annotation.RequestScope; * *

    Sequence is removed with Presentation API version 3.0. Canvases are added to the Manifest items property instead. *

    + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java index 610ec571d9..aef979b635 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageContentGenerator.java @@ -18,6 +18,9 @@ import de.digitalcollections.iiif.model.sharedcanvas.Resource; * Presentation API version 2.1.1: The ImageContent entity is contained in the "resource" * field of annotations with motivation "sc:painting". Image resources, and only image resources, * are included in the image's property of the canvas. This changes in API version 3.0. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ public class ImageContentGenerator implements IIIFResource { diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageServiceGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageServiceGenerator.java index 9a39945116..29b9072949 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageServiceGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ImageServiceGenerator.java @@ -13,6 +13,9 @@ import de.digitalcollections.iiif.model.image.ImageService; /** * This service generator wraps the image service property model. An image service * annotation is added to each canvas annotation. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ public class ImageServiceGenerator implements IIIFService { diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java index 70f56322dd..8072692640 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/ManifestGenerator.java @@ -39,6 +39,9 @@ import org.springframework.web.context.annotation.RequestScope; * * Please note that this is a request scoped bean. This means that for each http request a * different instance will be initialized by Spring and used to serve this specific request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/MetadataEntryGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/MetadataEntryGenerator.java index bab4dbecae..5eb92dfa4d 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/MetadataEntryGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/MetadataEntryGenerator.java @@ -13,6 +13,9 @@ import org.dspace.core.I18nUtil; /** * Wraps the domain model metadata property. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ public class MetadataEntryGenerator implements IIIFValue { diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java index cc358788e2..726f789535 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java @@ -24,6 +24,9 @@ import org.dspace.app.iiif.service.RangeService; * * This is used to populate the "structures" element of the Manifest. The structure is derived from the iiif.toc * metadata and the ordered sequence of bitstreams (canvases) + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ public class RangeGenerator implements IIIFResource { diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AbstractResourceService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AbstractResourceService.java index c7ef01984f..79530e1d3c 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AbstractResourceService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AbstractResourceService.java @@ -17,6 +17,9 @@ import org.springframework.beans.factory.annotation.Autowired; /** * Base class for services. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ public abstract class AbstractResourceService { /** diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AnnotationListService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AnnotationListService.java index fa71fae70f..e738b80105 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AnnotationListService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/AnnotationListService.java @@ -31,6 +31,9 @@ import org.springframework.web.context.annotation.RequestScope; * This service provides methods for creating an {@code Annotation List}. There should be a single instance of * this service per request. The {@code @RequestScope} provides a single instance created and available during * complete lifecycle of the HTTP request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasLookupService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasLookupService.java index 5c900d0a1c..d9c9478804 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasLookupService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasLookupService.java @@ -24,6 +24,9 @@ import org.springframework.web.context.annotation.RequestScope; * This service provides methods for creating a single {@code Canvas}. There should be a single instance of * this service per request. The {@code @RequestScope} provides a single instance created and available during * complete lifecycle of the HTTP request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component 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 1dac7da8bf..bd9d0bd4f2 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 @@ -35,6 +35,9 @@ import org.springframework.web.context.annotation.RequestScope; * This service provides methods for creating {@code Canvases}. There should be a single instance of * this service per request. The {@code @RequestScope} provides a single instance created and available during * complete lifecycle of the HTTP request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ImageContentService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ImageContentService.java index 3ef28d91c6..754e8b9bfa 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ImageContentService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ImageContentService.java @@ -20,6 +20,9 @@ import org.springframework.web.context.annotation.RequestScope; * This service provides methods for creating a {@code Image Resource} annotation. There should be a single instance of * this service per request. The {@code @RequestScope} provides a single instance created and available during * complete lifecycle of the HTTP request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component 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 ffe533e7bd..373c480a0f 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 @@ -43,6 +43,9 @@ import org.springframework.web.context.annotation.RequestScope; * prototype (that will turn in a request scope when injected in a request scope * bean). The generators for top-level domain objects need to be request scoped as they act as a builder * storing the object state during each incremental building step until the final object is returned (IIIF Resource). + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java index 54ec70ec0f..2021572e5c 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java @@ -17,6 +17,9 @@ import org.springframework.web.context.annotation.RequestScope; * This service provides methods for creating a {@code Range}. There should be a single instance of this service * per request. The {@code @RequestScope} provides a single instance created and available during complete lifecycle * of the HTTP request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RelatedService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RelatedService.java index 94550172ee..a29c6466b6 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RelatedService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RelatedService.java @@ -17,6 +17,9 @@ import org.springframework.web.context.annotation.RequestScope; * This service provides methods for creating a {@code related} annotation. There should be a single instance of * this service per request. The {@code @RequestScope} provides a single instance created and available during * complete lifecycle of the HTTP request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SeeAlsoService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SeeAlsoService.java index f4eb22ce88..f4bd8c0348 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SeeAlsoService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SeeAlsoService.java @@ -18,6 +18,9 @@ import org.springframework.web.context.annotation.RequestScope; * This service provides methods for creating {@code seAlso} external link. There should be a single instance of * this service per request. The {@code @RequestScope} provides a single instance created and available during * complete lifecycle of the HTTP request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java index cad81c382b..0907bf5d79 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java @@ -23,6 +23,9 @@ import org.springframework.web.context.annotation.RequestScope; * This service provides methods for creating a {@code Sequence}. There should be a single instance of * this service per request. The {@code @RequestScope} provides a single instance created and available during * complete lifecycle of the HTTP request. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) */ @RequestScope @Component From 534a16254cadf17b55edcf80e7b1b3499deb347b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 18 Oct 2021 15:42:37 +0200 Subject: [PATCH 0425/1254] minor update --- ...09.24__Move_entity_type_from_item_template_to_collection.sql | 2 +- ...09.24__Move_entity_type_from_item_template_to_collection.sql | 2 +- ...09.24__Move_entity_type_from_item_template_to_collection.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql index f750a2542e..5a6abda041 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql @@ -25,4 +25,4 @@ WHERE dspace_object_id IN (SELECT template_item_id IN (SELECT metadata_field_id FROM metadatafieldregistry mfr LEFT JOIN metadataschemaregistry msr ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE msr.short_id = 'dspace' AND mfr.element = 'entity' AND mfr.qualifier = 'type') \ No newline at end of file + WHERE msr.short_id = 'dspace' AND mfr.element = 'entity' AND mfr.qualifier = 'type'); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql index f750a2542e..5a6abda041 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql @@ -25,4 +25,4 @@ WHERE dspace_object_id IN (SELECT template_item_id IN (SELECT metadata_field_id FROM metadatafieldregistry mfr LEFT JOIN metadataschemaregistry msr ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE msr.short_id = 'dspace' AND mfr.element = 'entity' AND mfr.qualifier = 'type') \ No newline at end of file + WHERE msr.short_id = 'dspace' AND mfr.element = 'entity' AND mfr.qualifier = 'type'); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql index f750a2542e..5a6abda041 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.09.24__Move_entity_type_from_item_template_to_collection.sql @@ -25,4 +25,4 @@ WHERE dspace_object_id IN (SELECT template_item_id IN (SELECT metadata_field_id FROM metadatafieldregistry mfr LEFT JOIN metadataschemaregistry msr ON mfr.metadata_schema_id = msr.metadata_schema_id - WHERE msr.short_id = 'dspace' AND mfr.element = 'entity' AND mfr.qualifier = 'type') \ No newline at end of file + WHERE msr.short_id = 'dspace' AND mfr.element = 'entity' AND mfr.qualifier = 'type'); \ No newline at end of file From 7764ccb00fbed561b5e952aee48a3959a2749d5f Mon Sep 17 00:00:00 2001 From: April Herron Date: Mon, 18 Oct 2021 13:45:31 -0400 Subject: [PATCH 0426/1254] DS-2904 GA Code cleanup --- .../dspace/google/GoogleAnalyticsEvent.java | 56 ++++++++++++++++--- .../google/GoogleAsyncEventListener.java | 49 ++++++++-------- dspace/config/dspace.cfg | 1 + 3 files changed, 72 insertions(+), 34 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleAnalyticsEvent.java b/dspace-api/src/main/java/org/dspace/google/GoogleAnalyticsEvent.java index e3404796c4..9e9751337c 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleAnalyticsEvent.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleAnalyticsEvent.java @@ -23,31 +23,46 @@ public class GoogleAnalyticsEvent { private long time; GoogleAnalyticsEvent(String cid, String uip, String ua, String dr, String dp, String dt, long time) { - this.cid = cid; // Client ID - this.uip = uip; // User IP - this.ua = ua; // User Agent - this.dr = dr; // Document Referrer - this.dp = dp; // Document Path - this.dt = dt; // Document Title - this.time = time; // Time of event + this.cid = cid; + this.uip = uip; + this.ua = ua; + this.dr = dr; + this.dp = dp; + this.dt = dt; + this.time = time; } + /** + * Return Client ID + */ public String getCid() { return cid; } + /** + * Set Client ID + */ public void setCid(String cid) { this.cid = cid; } + /** + * Return User IP + */ public String getUip() { return uip; } + /** + * Set User IP + */ public void setUip(String uip) { this.uip = uip; } + /** + * Returns User Agent + */ public String getUa() { if (ua == null) { return ""; @@ -56,10 +71,16 @@ public class GoogleAnalyticsEvent { } } + /** + * Set User Agent + */ public void setUa(String ua) { this.ua = ua; } + /** + * Return Document Referrer + */ public String getDr() { if (dr == null) { return ""; @@ -68,30 +89,51 @@ public class GoogleAnalyticsEvent { } } + /** + * Set Document Referrer + */ public void setDr(String dr) { this.dr = dr; } + /** + * Return Document Path + */ public String getDp() { return dp; } + /** + * Set Document Path + */ public void setDp(String dp) { this.dp = dp; } + /** + * Return Document Title + */ public String getDt() { return dt; } + /** + * Set Document Title + */ public void setDt(String dt) { this.dt = dt; } + /** + * Return Time of event + */ public long getTime() { return time; } + /** + * Set Time of event + */ public void setTime(long time) { this.time = time; } diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java index 03c54a1eb9..cf5c40976d 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java @@ -20,7 +20,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import javax.servlet.http.HttpServletRequest; import com.google.common.base.Throwables; import org.apache.commons.collections.Buffer; @@ -36,6 +35,7 @@ import org.apache.log4j.Logger; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.service.ClientInfoService; import org.dspace.services.ConfigurationService; import org.dspace.services.model.Event; import org.dspace.usage.AbstractUsageEventListener; @@ -52,6 +52,8 @@ import org.springframework.beans.factory.annotation.Autowired; public class GoogleAsyncEventListener extends AbstractUsageEventListener { private static final int MAX_TIME_SINCE_EVENT = 14400000; + // 20 is the event max set by the GA API + private static final int GA_MAX_EVENTS = 20; private static final String ANALYTICS_BATCH_ENDPOINT = "https://www.google-analytics.com/batch"; private static Logger log = Logger.getLogger(GoogleAsyncEventListener.class); private static String analyticsKey; @@ -64,6 +66,9 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { @Autowired(required = true) ConfigurationService configurationService; + @Autowired(required = true) + ClientInfoService clientInfoService; + @PostConstruct public void init() { analyticsKey = configurationService.getProperty("google.analytics.key"); @@ -86,17 +91,26 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { if (ue.getAction() == UsageEvent.Action.VIEW && ue.getObject().getType() == Constants.BITSTREAM) { - // Client Id, should uniquely identify the user or device. If we have a session id for the user - // then lets use it, else generate a UUID. + // Client ID, should uniquely identify the user or device. If we have an X-CORRELATION-ID + // header or a session ID for the user, then lets use it, othwerwise generate a UUID. String cid; - if (ue.getRequest().getSession(false) != null) { + if (ue.getRequest().getHeader("X-CORRELATION-ID") != null) { + cid = ue.getRequest().getHeader("X-CORRELATION-ID"); + } else if (ue.getRequest().getSession(false) != null) { cid = ue.getRequest().getSession().getId(); } else { cid = UUID.randomUUID().toString(); } - buffer.add(new GoogleAnalyticsEvent(cid, getIPAddress(ue.getRequest()), ue.getRequest() - .getHeader("USER-AGENT"), ue.getRequest().getHeader("referer"), ue.getRequest() - .getRequestURI() + "?" + ue.getRequest().getQueryString(), + // Prefer the X-REFERRER header, otherwise falback to the referrer header + String referrer; + if (ue.getRequest().getHeader("X-REFERRER") != null) { + referrer = ue.getRequest().getHeader("X-REFERRER"); + } else { + referrer = ue.getRequest().getHeader("referer"); + } + buffer.add(new GoogleAnalyticsEvent(cid, clientInfoService.getClientIp(ue.getRequest()), + ue.getRequest().getHeader("USER-AGENT"), referrer, + ue.getRequest() .getRequestURI() + "?" + ue.getRequest().getQueryString(), getObjectName(ue), System.currentTimeMillis())); } } catch (Exception e) { @@ -140,25 +154,6 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { } - private String getIPAddress(HttpServletRequest request) { - String clientIP = request.getRemoteAddr(); - if (configurationService.getBooleanProperty("useProxies", false) && - request.getHeader("X-Forwarded-For") != null) { - /* This header is a comma delimited list */ - for (String xfip : request.getHeader("X-Forwarded-For").split(",")) { - /* proxy itself will sometime populate this header with the same value in - remote address. ordering in spec is vague, we'll just take the last - not equal to the proxy - */ - if (!request.getHeader("X-Forwarded-For").contains(clientIP)) { - clientIP = xfip.trim(); - } - } - } - - return clientIP; - } - @PreDestroy public void destroy() throws InterruptedException { destroyed = true; @@ -177,7 +172,7 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { StringBuilder request = null; List events = new ArrayList<>(); Iterator iterator = buffer.iterator(); - for (int x = 0; x < 20 && iterator.hasNext(); x++) { + for (int x = 0; x < GA_MAX_EVENTS && iterator.hasNext(); x++) { GoogleAnalyticsEvent event = (GoogleAnalyticsEvent) iterator.next(); events.add(event); if ((System.currentTimeMillis() - event.getTime()) < MAX_TIME_SINCE_EVENT) { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index e833b8a91f..1f02d0bfa2 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1510,6 +1510,7 @@ log.report.dir = ${dspace.dir}/log # _uacct = "UA-XXXXXXX-X" # Take this key (just the UA-XXXXXX-X part) and place it here in this parameter. # google.analytics.key=UA-XXXXXX-X +# The max number of events held in the GA buffer (default: 256) # google.analytics.buffer.limit=256 #---------------------------------------------------------------# From 00cd727e0fa355c3c698da97a25a72c3a4dc6470 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 18 Oct 2021 14:08:55 -0700 Subject: [PATCH 0427/1254] Updated to a method signature. --- .../java/org/dspace/app/iiif/service/ManifestService.java | 2 +- .../java/org/dspace/app/iiif/service/SequenceService.java | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) 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 373c480a0f..794fbb46ca 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 @@ -127,7 +127,7 @@ public class ManifestService extends AbstractResourceService { addThumbnail(item, context); addRanges(context, item, manifestId); manifestGenerator.addSequence( - sequenceService.getSequence(item, context)); + sequenceService.getSequence(item)); addRendering(item, context); addSeeAlso(item); } diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java index 0907bf5d79..7914ae1100 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SequenceService.java @@ -51,14 +51,12 @@ public class SequenceService extends AbstractResourceService { } /** - * Returns a sequence generator that has been configured with canvases and an optional - * rendering link. (@abollini will update.) + * Returns a sequence generator that has been configured with canvases. (@abollini will update.) * * @param item the DSpace item - * @param context the DSpace context * @return a sequence generator */ - public CanvasItemsGenerator getSequence(Item item, Context context) { + public CanvasItemsGenerator getSequence(Item item) { sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0"); return sequenceGenerator; From 84fdc9567c0595f2275e3cd5cd2977984a3f69aa Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 18 Oct 2021 17:04:32 -0500 Subject: [PATCH 0428/1254] Add docker-compose script for IIIF server --- .../docker-compose/docker-compose-iiif.yml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 dspace/src/main/docker-compose/docker-compose-iiif.yml diff --git a/dspace/src/main/docker-compose/docker-compose-iiif.yml b/dspace/src/main/docker-compose/docker-compose-iiif.yml new file mode 100644 index 0000000000..f4d38fe76e --- /dev/null +++ b/dspace/src/main/docker-compose/docker-compose-iiif.yml @@ -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/ +# + +# +# Test environment for DSpace + Cantaloupe for IIIF support. See README for instructions. +# This should NEVER be used in production scenarios. +# +version: '3.7' +networks: + dspacenet: +services: + dspace-iiif: + container_name: dspace-iiif + depends_on: + - dspace + # Using UCLA Library image as it seems to be most maintained at this time. There is no official image. + image: uclalibrary/cantaloupe:5.0.4-0 + networks: + dspacenet: + ports: + - '8182:8182' + # For a guide of environment variables that can be used, see + # https://github.com/UCLALibrary/docker-cantaloupe/tree/main/src/main/docker/configs + environment: + # Enable the /admin UI for Cantaloupe + CANTALOUPE_ENDPOINT_ADMIN_ENABLED: 'true' + CANTALOUPE_ENDPOINT_ADMIN_USERNAME: 'admin' + CANTALOUPE_ENDPOINT_ADMIN_SECRET: 'admin' + # Configure Cantaloupe to use HTTP to load images, point it at the REST API /bitstreams/[uuid]/content endpoint + CANTALOUPE_SOURCE_STATIC: 'HttpSource' + # Notice this URL accesses the 'dspace' container, port 8080, which is the container running the REST API. + CANTALOUPE_HTTPSOURCE_BASICLOOKUPSTRATEGY_URL_PREFIX: 'http://dspace:8080/server/api/core/bitstreams/' + CANTALOUPE_HTTPSOURCE_BASICLOOKUPSTRATEGY_URL_SUFFIX: '/content' \ No newline at end of file From 736d1dbeaac0a2e61ab0d4d3eb439036711b6d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Tue, 19 Oct 2021 21:57:40 +0100 Subject: [PATCH 0429/1254] change to extend openaire lookup from AbstractExternalDataProvider --- .../impl/OpenAIREFundingDataProvider.java | 4 ++-- .../config/spring/api/external-openaire.xml | 15 +++++++++++---- .../config/spring/api/external-openaire.xml | 5 +++++ dspace/config/spring/api/external-openaire.xml | 15 +++++++++++---- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java index 76cc9753b5..3dcd7d16a6 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java @@ -32,7 +32,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.OpenAIRERestConnector; import org.dspace.external.model.ExternalDataObject; -import org.dspace.external.provider.ExternalDataProvider; +import org.dspace.external.provider.AbstractExternalDataProvider; import org.springframework.beans.factory.annotation.Autowired; /** @@ -42,7 +42,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author paulo-graca * */ -public class OpenAIREFundingDataProvider implements ExternalDataProvider { +public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { /** * log4j logger diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index d2d5be9c39..e10d04a16f 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -10,9 +10,16 @@
    - - - - + + + + + + Project + + + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index 264275c1c6..9db02440e4 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -22,6 +22,11 @@ init-method="init"> + + + Project + +
    \ No newline at end of file diff --git a/dspace/config/spring/api/external-openaire.xml b/dspace/config/spring/api/external-openaire.xml index add431e542..f483ce7210 100644 --- a/dspace/config/spring/api/external-openaire.xml +++ b/dspace/config/spring/api/external-openaire.xml @@ -10,10 +10,17 @@
    - - - - + + + + + + Project + + + From c7c04db576519d7035cede9d82211f159b8f85bf Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 20 Oct 2021 15:06:47 -0700 Subject: [PATCH 0430/1254] Optional iiif configuration and javadoc. --- .../test/data/dspaceFolder/config/local.cfg | 5 + .../org/dspace/app/iiif/IIIFController.java | 3 + .../iiif/service/SearchAnnotationService.java | 24 ++- .../app/iiif/service/SearchService.java | 2 +- .../iiif/service/WordHighlightSolrSearch.java | 13 +- .../app/rest/iiif/IIIFControllerIT.java | 137 +++++++++++++++++- dspace/config/dspace.cfg | 3 +- dspace/config/modules/iiif.cfg | 6 +- 8 files changed, 171 insertions(+), 22 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 74dfe082dd..3c19a68e9f 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -80,6 +80,11 @@ loglevel.dspace = INFO # loglevel.dspace: Log level for all DSpace-specific code (org.dspace.*) # Possible values (from most to least info): DEBUG, INFO, WARN, ERROR, FATAL +######################## +# IIIF TEST SETTINGS # +######################## +iiif.enabled = true +event.dispatcher.default.consumers = versioning, discovery, eperson, iiif ########################################### # CUSTOM UNIT / INTEGRATION TEST SETTINGS # diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java index a1e265ccfc..67139a0f0d 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java @@ -14,6 +14,7 @@ import org.dspace.services.RequestService; import org.dspace.utils.DSpace; import org.dspace.web.ContextUtil; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -29,6 +30,8 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping("/iiif") +// Only enable this controller if "iiif.enabled=true" +@ConditionalOnProperty("iiif.enabled") public class IIIFController { @Autowired diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchAnnotationService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchAnnotationService.java index 776e399df4..15f0879c5f 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchAnnotationService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchAnnotationService.java @@ -9,12 +9,34 @@ package org.dspace.app.iiif.service; import java.util.UUID; +/** + * Interface for IIIF Search API implementations. + */ public interface SearchAnnotationService { + /** + * Initializes required values. + * + * @param endpoint the iiif service endpoint + * @param manifestId the id of the manifest to search within + */ void initializeQuerySettings(String endpoint, String manifestId); - String getSolrSearchResponse(UUID uuid, String query); + /** + * Executes the Search API solr query and returns iiif search result + * annotations. + * + * @param query encoded query terms + * @return iiif json response + */ + String getSearchResponse(UUID uuid, String query); + /** + * Tests to see if the plugin is configured in iiif.cfg. + * + * @param className the canonical name of class + * @return true if provided value matches plugin class name + */ boolean useSearchPlugin(String className); } diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchService.java index 22617ebd4f..be039477b5 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/SearchService.java @@ -52,7 +52,7 @@ public class SearchService extends AbstractResourceService { for (SearchAnnotationService service : annotationService) { if (service.useSearchPlugin(searchPlugin)) { service.initializeQuerySettings(IIIF_ENDPOINT, getManifestId(uuid)); - return service.getSolrSearchResponse(uuid, query); + return service.getSearchResponse(uuid, query); } } } diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java index 4763284334..0e614fae2a 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/WordHighlightSolrSearch.java @@ -78,25 +78,14 @@ public class WordHighlightSolrSearch implements SearchAnnotationService { return className.contentEquals(WordHighlightSolrSearch.class.getCanonicalName()); } - /** - * The query requires these values before it can execute - * @param endpoint the search service endpoint - * @param manifestId the idea of the manifest to search within - */ @Override public void initializeQuerySettings(String endpoint, String manifestId) { this.endpoint = endpoint; this.manifestId = manifestId; } - /** - * Executes the Search API solr query. - * @param query encoded query terms - * - * @return json query response - */ @Override - public String getSolrSearchResponse(UUID uuid, String query) { + public String getSearchResponse(UUID uuid, String query) { String json = ""; ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); String solrService = configurationService.getProperty("iiif.search.url"); 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 0300ce7540..e605a3aed0 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 @@ -8,6 +8,7 @@ package org.dspace.app.rest.iiif; 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.result.MockMvcResultMatchers.jsonPath; @@ -33,6 +34,7 @@ import org.dspace.content.service.ItemService; 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; @@ -43,6 +45,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { @Autowired ItemService itemService; + @Test public void disabledTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -1028,9 +1031,9 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { } @Test - public void findOneWithCacheEvictionAfterItemUpdate() throws Exception { + public void findOneWithCacheEvictionAfterBitstreamUpdate() throws Exception { String patchRequestBody = - "[{\"op\": \"replace\",\"path\": \"/metadata/dc.title/0/value\",\"value\": \"Public item 1 (revised)\"}]"; + "[{\"op\": \"replace\",\"path\": \"/metadata/iiif.label/0/value\",\"value\": \"Test label\"}]"; context.turnOffAuthorisationSystem(); @@ -1061,6 +1064,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { bitstream2 = BitstreamBuilder .createBitstream(context, publicItem1, is) + .withIIIFLabel("Original label") .withName("Bitstream2.png") .withMimeType("image/png") .build(); @@ -1068,6 +1072,127 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); + // Default canvas size and label. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata[0].label", is("Title"))) + .andExpect(jsonPath("$.metadata[0].value", is("Public item 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Original label"))); + + String token = getAuthToken(admin.getEmail(), password); + + // The Bitstream update should also remove the manifest from the cache. + getClient(token).perform(patch("/api/core/bitstreams/" + bitstream2.getID()) + .content(patchRequestBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + // Verify that the updated canvas label is in the manifest. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Test label"))); + } + + @Test + public void findOneWithCacheEvictionAfterBitstreamRemoval() 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(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withIIIFLabel("Original label") + .withName("Bitstream2.png") + .withMimeType("image/png") + .build(); + } + + context.restoreAuthSystemState(); + + // Default canvas size and label. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata[0].label", is("Title"))) + .andExpect(jsonPath("$.metadata[0].value", is("Public item 1"))) + .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Original label"))); + + String token = getAuthToken(admin.getEmail(), password); + + // The Bitstream deletion should also remove the manifest from the cache. + getClient(token).perform(delete("/api/core/bitstreams/" + bitstream2.getID())) + .andExpect(status().isNoContent()); + + // Verify that the updated manifest has only a single canvas. + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sequences[0].canvases.length()", Matchers.equalTo(1))); + } + + @Test + public void findOneWithCacheEvictionAfterItemUpdate() throws Exception { + String patchRequestBody = + "[{\"op\": \"replace\",\"path\": \"/metadata/dc.title/0/value\",\"value\": \"Public item (revised)\"}]"; + + 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(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream1 = null; + Bitstream bitstream2 = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .build(); + } + + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream2 = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Bitstream2.png") + .withMimeType("image/png") + .build(); + } + + context.restoreAuthSystemState(); + // Default canvas size and label. getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) @@ -1078,14 +1203,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { // The Item update should also remove the manifest from the cache. getClient(token).perform(patch("/api/core/items/" + publicItem1.getID()) - .content(patchRequestBody) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()); + .content(patchRequestBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); // Verify that the updated title is in the manifest. getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.metadata[0].value", is("Public item 1 (revised)"))); + .andExpect(jsonPath("$.metadata[0].value", is("Public item (revised)"))); } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 1207c9ecff..335f4cf16a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -696,7 +696,8 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. -event.dispatcher.default.consumers = versioning, discovery, eperson, iiif +# Add iiif here, if you are using dspace-iiif. +event.dispatcher.default.consumers = versioning, discovery, eperson # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index 53d3ca98c6..d6820b5305 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -1,11 +1,15 @@ #### IIIF CONFIGURATION #### +# To enable the IIIF service, set to true. Note that to use IIIF you must also provide +# an image server. +iiif.enabled = false + # Public base url of a iiif server able to serve DSpace images. The bitstream # uuid is appended to this URL iiif.image.server = http://localhost:8182/iiif/2/ # Base URL of the search service used to support the (experimental) IIIF Search # capability. The actual path will depend on how search is being supported. -# This example assumes the solr plugin https://dbmdz.github.io/solr-ocrhighlighting/ +# This example uses the solr plugin https://dbmdz.github.io/solr-ocrhighlighting/ # iiif.search.url = ${solr.server}/word_highlighting # The search plugin used to support (experimental) IIIF Search. From 33966cc4cbd7da0064fbdc737cc455272feb3303 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 20 Oct 2021 15:38:55 -0700 Subject: [PATCH 0431/1254] Unused import in test. --- .../test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java | 2 -- 1 file changed, 2 deletions(-) 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 e605a3aed0..00ed1ba11e 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 @@ -34,7 +34,6 @@ import org.dspace.content.service.ItemService; 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; @@ -45,7 +44,6 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { @Autowired ItemService itemService; - @Test public void disabledTest() throws Exception { context.turnOffAuthorisationSystem(); From 0198efce6950f50003e230009c954ef59c76f364 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 20 Oct 2021 16:08:53 -0700 Subject: [PATCH 0432/1254] Updated classes to call ContextUtil.obtainContext with HttpServletRequest objects. --- .../org/dspace/app/rest/converter/VersionHistoryConverter.java | 2 +- .../security/ExtractorOfAInprogressSubmissionInformations.java | 2 +- .../security/VersionRestPatchPermissionEvaluatorPlugin.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java index 97c1be7e26..81ae433f09 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VersionHistoryConverter.java @@ -67,7 +67,7 @@ public class VersionHistoryConverter implements DSpaceConverter Date: Thu, 21 Oct 2021 10:24:23 -0500 Subject: [PATCH 0433/1254] Fix main branch IT compilation errors after merging #7962 --- .../java/org/dspace/app/rest/VersionRestRepositoryIT.java | 6 +++--- .../app/rest/authorization/CanCreateVersionFeatureIT.java | 6 +++--- .../app/rest/authorization/CanDeleteVersionFeatureIT.java | 4 ++-- .../app/rest/authorization/CanEditVersionFeatureIT.java | 4 ++-- .../app/rest/authorization/CanManageVersionsFeatureIT.java | 4 ++-- 5 files changed, 12 insertions(+), 12 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 c5e7f04c3b..86d66dec32 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 @@ -722,13 +722,13 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { Collection col = CollectionBuilder.createCollection(context, rootCommunity) .withName("Collection 1") + .withEntityType("Publication") .build(); Item itemA = ItemBuilder.createItem(context, col) .withTitle("Public item") .withIssueDate("2021-04-19") .withAuthor("Doe, John") - .withEntityType("Publication") .withSubject("ExtraEntry") .build(); @@ -752,13 +752,13 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { Collection col = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection test") + .withEntityType("Publication") .build(); Item item = ItemBuilder.createItem(context, col) .withTitle("Public test item") .withIssueDate("2021-04-27") .withAuthor("Doe, John") - .withEntityType("Publication") .withSubject("ExtraEntry") .build(); @@ -796,6 +796,7 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { Collection col = CollectionBuilder.createCollection(context, rootCommunity) .withName("Collection 1") + .withEntityType("Publication") .withSubmitterGroup(eperson) .build(); @@ -803,7 +804,6 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { .withTitle("Public item") .withIssueDate("2021-04-19") .withAuthor("Doe, John") - .withEntityType("Publication") .withSubject("ExtraEntry") .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java index 81302d891c..4cbe9e4252 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanCreateVersionFeatureIT.java @@ -315,13 +315,13 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest Collection col = CollectionBuilder.createCollection(context, rootCommunity) .withName("Collection 1") + .withEntityType("Publication") .withAdminGroup(eperson) .build(); Item itemA = ItemBuilder.createItem(context, col) .withTitle("Public item") .withIssueDate("2021-04-19") - .withEntityType("Publication") .withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); @@ -361,12 +361,12 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest Collection col = CollectionBuilder.createCollection(context, rootCommunity) .withName("Collection 1") + .withEntityType("Publication") .build(); Item itemA = ItemBuilder.createItem(context, col) .withTitle("Public item") .withIssueDate("2021-04-19") - .withEntityType("Publication") .withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); @@ -409,13 +409,13 @@ public class CanCreateVersionFeatureIT extends AbstractControllerIntegrationTest Collection col = CollectionBuilder.createCollection(context, rootCommunity) .withName("Collection 1") + .withEntityType("Publication") .withSubmitterGroup(eperson) .build(); Item itemA = ItemBuilder.createItem(context, col) .withTitle("Public item") .withIssueDate("2021-04-19") - .withEntityType("Publication") .withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java index 0f09380d70..ac59de54eb 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanDeleteVersionFeatureIT.java @@ -270,12 +270,12 @@ public class CanDeleteVersionFeatureIT extends AbstractControllerIntegrationTest Collection col = CollectionBuilder.createCollection(context, rootCommunity) .withName("Collection 1") + .withEntityType("Publication") .build(); Item itemA = ItemBuilder.createItem(context, col) .withTitle("Public item") .withIssueDate("2021-04-19") - .withEntityType("Publication") .withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); @@ -318,12 +318,12 @@ public class CanDeleteVersionFeatureIT extends AbstractControllerIntegrationTest Collection col = CollectionBuilder.createCollection(context, rootCommunity) .withName("Collection 1") + .withEntityType("Publication") .build(); Item itemA = ItemBuilder.createItem(context, col) .withTitle("Public item") .withIssueDate("2021-04-19") - .withEntityType("Publication") .withAuthor("Doe, John") .withSubject("ExtraEntry") .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java index 4e6339aabd..5723637f0b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanEditVersionFeatureIT.java @@ -226,6 +226,7 @@ public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) .withName("Collection 1") + .withEntityType("Publication") .withSubmitterGroup(eperson) .withAdminGroup(adminCol1) .build(); @@ -234,7 +235,6 @@ public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { .withTitle("Public item") .withIssueDate("2021-04-19") .withAuthor("Doe, John") - .withEntityType("Publication") .withSubject("ExtraEntry") .build(); @@ -289,6 +289,7 @@ public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) .withName("Collection 1") + .withEntityType("Publication") .withSubmitterGroup(eperson) .withAdminGroup(adminCol1) .build(); @@ -297,7 +298,6 @@ public class CanEditVersionFeatureIT extends AbstractControllerIntegrationTest { .withTitle("Public item") .withIssueDate("2021-04-19") .withAuthor("Doe, John") - .withEntityType("Publication") .withSubject("ExtraEntry") .build(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java index f11017449a..1a1f8f9e28 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanManageVersionsFeatureIT.java @@ -247,6 +247,7 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) .withName("Collection 1") + .withEntityType("Publication") .withSubmitterGroup(eperson) .withAdminGroup(adminCol1) .build(); @@ -260,7 +261,6 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes .withTitle("Public item") .withIssueDate("2021-04-19") .withAuthor("Doe, John") - .withEntityType("Publication") .withSubject("ExtraEntry") .build(); @@ -332,6 +332,7 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes Collection col1 = CollectionBuilder.createCollection(context, subCommunityA) .withName("Collection 1") + .withEntityType("Publication") .withSubmitterGroup(eperson) .withAdminGroup(adminCol1) .build(); @@ -345,7 +346,6 @@ public class CanManageVersionsFeatureIT extends AbstractControllerIntegrationTes .withTitle("Public item") .withIssueDate("2021-04-19") .withAuthor("Doe, John") - .withEntityType("Publication") .withSubject("ExtraEntry") .build(); From c4e5725bdd918838b460991875af57c621b79e42 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 21 Oct 2021 10:08:45 -0700 Subject: [PATCH 0434/1254] Unused import. --- .../security/ExtractorOfAInprogressSubmissionInformations.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java index 2268061f44..dd2c8602d8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ExtractorOfAInprogressSubmissionInformations.java @@ -10,7 +10,6 @@ import java.sql.SQLException; import java.util.Objects; import java.util.UUID; import javax.annotation.Nullable; -import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; From 3e05832a38b21b595e1d6ccdc62b37f6aa480839 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 21 Oct 2021 13:24:22 -0500 Subject: [PATCH 0435/1254] Fix two fragile tests which broke whenever configs changed --- .../rest/ExternalSourcesRestControllerIT.java | 59 ++++++++++++------- .../rest/matcher/ExternalSourceMatcher.java | 5 ++ 2 files changed, 44 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 05ca955e5c..5818add4e1 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 @@ -13,6 +13,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Arrays; +import java.util.List; import org.dspace.app.rest.matcher.EntityTypeMatcher; import org.dspace.app.rest.matcher.ExternalSourceEntryMatcher; @@ -22,6 +23,7 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.EntityTypeBuilder; import org.dspace.content.EntityType; import org.dspace.external.provider.AbstractExternalDataProvider; +import org.dspace.external.provider.ExternalDataProvider; import org.dspace.external.service.ExternalDataService; import org.hamcrest.Matchers; import org.junit.Test; @@ -142,25 +144,34 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati @Test public void findExternalSourcesByEntityTypeTest() throws Exception { + List publicationProviders = + externalDataService.getExternalDataProvidersForEntityType("Publication"); + List journalProviders = + externalDataService.getExternalDataProvidersForEntityType("Journal"); + getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Publication")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( - ExternalSourceMatcher.matchExternalSource("mock", "mock", false), - ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false) + // Expect *at least* 3 Publication sources + .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems( + ExternalSourceMatcher.matchExternalSource(publicationProviders.get(0)), + ExternalSourceMatcher.matchExternalSource(publicationProviders.get(1)), + ExternalSourceMatcher.matchExternalSource(publicationProviders.get(2)) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(publicationProviders.size()))); getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Journal")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( - ExternalSourceMatcher.matchExternalSource("mock", "mock", false), - ExternalSourceMatcher.matchExternalSource("sherpaJournalIssn", "sherpaJournalIssn",false), - ExternalSourceMatcher.matchExternalSource("sherpaJournal", "sherpaJournal", false), - ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false) + // 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)), + ExternalSourceMatcher.matchExternalSource(journalProviders.get(4)) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(journalProviders.size()))); } @Test @@ -171,28 +182,36 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati @Test public void findExternalSourcesByEntityTypePaginationTest() throws Exception { + List journalProviders = + externalDataService.getExternalDataProvidersForEntityType("Journal"); + int numJournalProviders = journalProviders.size(); + + // If we return 2 per page, determine number of pages we expect + int pageSize = 2; + int numberOfPages = (int) Math.ceil((double) numJournalProviders / pageSize); + getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Journal") - .param("size", "2")) + .param("size", String.valueOf(pageSize))) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( - ExternalSourceMatcher.matchExternalSource("mock", "mock", false), - ExternalSourceMatcher.matchExternalSource("sherpaJournalIssn", "sherpaJournalIssn",false) + ExternalSourceMatcher.matchExternalSource(journalProviders.get(0)), + ExternalSourceMatcher.matchExternalSource(journalProviders.get(1)) ))) - .andExpect(jsonPath("$.page.totalPages", Matchers.is(2))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + .andExpect(jsonPath("$.page.totalPages", Matchers.is(numberOfPages))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(numJournalProviders))); getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Journal") .param("page", "1") - .param("size", "2")) + .param("size", String.valueOf(pageSize))) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( - ExternalSourceMatcher.matchExternalSource("sherpaJournal", "sherpaJournal", false), - ExternalSourceMatcher.matchExternalSource("pubmed", "pubmed", false) + ExternalSourceMatcher.matchExternalSource(journalProviders.get(2)), + ExternalSourceMatcher.matchExternalSource(journalProviders.get(3)) ))) - .andExpect(jsonPath("$.page.totalPages", Matchers.is(2))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + .andExpect(jsonPath("$.page.totalPages", Matchers.is(numberOfPages))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(numJournalProviders))); } @Test 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 92a94d48a7..ce0cda0b58 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 @@ -12,6 +12,7 @@ import static org.dspace.app.rest.test.AbstractControllerIntegrationTest.REST_SE import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; +import org.dspace.external.provider.ExternalDataProvider; import org.hamcrest.Matcher; public class ExternalSourceMatcher { @@ -19,6 +20,10 @@ public class ExternalSourceMatcher { private ExternalSourceMatcher() { } + public static Matcher matchExternalSource(ExternalDataProvider provider) { + return matchExternalSource(provider.getSourceIdentifier(), provider.getSourceIdentifier(), false); + } + public static Matcher matchExternalSource(String id, String name, boolean hierarchical) { return allOf( hasJsonPath("$.id", is(id)), From b6ec08746e5c674964a937cf28ff27cf8eb9cd91 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 21 Oct 2021 15:00:53 -0500 Subject: [PATCH 0436/1254] Disable broken test temporarily --- .../java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 8437ad9068..75df09607c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -30,6 +30,7 @@ import org.dspace.core.Constants; import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.external.service.ExternalDataService; import org.hamcrest.Matchers; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -353,6 +354,8 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$.page.number", is(1))); } + // TEST IS TEMPORARILY BROKEN ON MAIN. REQUIRES FIXING + @Ignore @Test public void findAllByAuthorizedExternalSource() throws Exception { context.turnOffAuthorisationSystem(); From 99230934a0482d62bfacaf0c07547c275bbc7ed7 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 21 Oct 2021 14:30:06 -0700 Subject: [PATCH 0437/1254] Removed unused property. --- .../src/main/java/org/dspace/app/iiif/IIIFController.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java index 67139a0f0d..491a94d565 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFController.java @@ -10,8 +10,6 @@ package org.dspace.app.iiif; import java.util.UUID; import org.dspace.core.Context; -import org.dspace.services.RequestService; -import org.dspace.utils.DSpace; import org.dspace.web.ContextUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -37,8 +35,6 @@ public class IIIFController { @Autowired IIIFServiceFacade iiifFacade; - protected RequestService requestService = new DSpace().getRequestService(); - /** * The manifest response contains sufficient information for the client to initialize * itself and begin to display something quickly to the user. The manifest resource From 0d9e2d317d11df7df2461c51836eba933cac9448 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 21 Oct 2021 15:35:09 -0700 Subject: [PATCH 0438/1254] Minor comment change. --- .../org/dspace/app/rest/security/WebSecurityConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index 627862c508..8ec70dfd34 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -79,7 +79,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // Configure authentication requirements for ${dspace.server.url}/api/ URL only - // NOTE: REST API is hardcoded to respond on /api/. Other modules (OAI, SWORD, etc) use other root paths. + // NOTE: REST API is hardcoded to respond on /api/. Other modules (OAI, SWORD, IIIF, etc) use other root paths. http.requestMatchers() .antMatchers("/api/**", "/iiif/**") .and() From 0191bf1431033e8aeb222e74f7150689b9a29eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Gra=C3=A7a?= Date: Fri, 22 Oct 2021 11:41:58 +0100 Subject: [PATCH 0439/1254] make adjustments to findExternalSourcesByEntityTypeTest --- .../dspace/app/rest/ExternalSourcesRestControllerIT.java | 6 ++---- 1 file changed, 2 insertions(+), 4 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 5818add4e1..9205c3b88e 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 @@ -155,8 +155,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati // Expect *at least* 3 Publication sources .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems( ExternalSourceMatcher.matchExternalSource(publicationProviders.get(0)), - ExternalSourceMatcher.matchExternalSource(publicationProviders.get(1)), - ExternalSourceMatcher.matchExternalSource(publicationProviders.get(2)) + ExternalSourceMatcher.matchExternalSource(publicationProviders.get(1)) ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(publicationProviders.size()))); @@ -168,8 +167,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati ExternalSourceMatcher.matchExternalSource(journalProviders.get(0)), ExternalSourceMatcher.matchExternalSource(journalProviders.get(1)), ExternalSourceMatcher.matchExternalSource(journalProviders.get(2)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(3)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(4)) + ExternalSourceMatcher.matchExternalSource(journalProviders.get(3)) ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(journalProviders.size()))); } From b4ff0dcf06b9a3a88568b02fe02fc657d0e61d7b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 22 Oct 2021 11:28:08 -0500 Subject: [PATCH 0440/1254] Update docker-compose README with details of dspace-iiif --- dspace/src/main/docker-compose/README.md | 11 +++++++++++ .../src/main/docker-compose/docker-compose-iiif.yml | 1 + 2 files changed, 12 insertions(+) diff --git a/dspace/src/main/docker-compose/README.md b/dspace/src/main/docker-compose/README.md index a0154ebdba..97d26bd8ee 100644 --- a/dspace/src/main/docker-compose/README.md +++ b/dspace/src/main/docker-compose/README.md @@ -49,6 +49,17 @@ docker-compose -p d7 up -d docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/docker-compose-angular.yml up -d ``` +## Run DSpace 7 REST with a IIIF Image Server from your branch +*Only useful for testing IIIF support in a development environment* + +This command starts our `dspace-iiif` container alongside the REST API. +That container provides a [Cantaloupe image server](https://cantaloupe-project.github.io/), +which can be used when IIIF support is enabled in DSpace (`iiif.enabled=true`). + +``` +docker-compose -p d7 -f docker-compose.yml -f dspace/src/main/docker-compose/docker-compose-iiif.yml up -d +``` + ## Run DSpace 7 REST and Shibboleth SP (in Apache) from your branch *Only useful for testing Shibboleth in a development environment* diff --git a/dspace/src/main/docker-compose/docker-compose-iiif.yml b/dspace/src/main/docker-compose/docker-compose-iiif.yml index f4d38fe76e..2ab58d9014 100644 --- a/dspace/src/main/docker-compose/docker-compose-iiif.yml +++ b/dspace/src/main/docker-compose/docker-compose-iiif.yml @@ -19,6 +19,7 @@ services: depends_on: - dspace # Using UCLA Library image as it seems to be most maintained at this time. There is no official image. + # https://hub.docker.com/r/uclalibrary/cantaloupe image: uclalibrary/cantaloupe:5.0.4-0 networks: dspacenet: From ee857f9412f1d707f00b481030043736db0c2338 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 22 Oct 2021 18:40:45 +0200 Subject: [PATCH 0441/1254] add support to enable/disable append-mode of DefaultReadGroups via conf --- .../org/dspace/content/ItemServiceImpl.java | 32 ++- .../rest/WorkspaceItemRestRepositoryIT.java | 205 ++++++++++++++++++ dspace/config/dspace.cfg | 1 + 3 files changed, 233 insertions(+), 5 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 6656028e7b..e1c6e21bd1 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -17,6 +17,7 @@ import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -1050,7 +1051,8 @@ prevent the generation of resource policy entry values with null dspace_object a for (ResourcePolicy defaultPolicy : defaultCollectionPolicies) { if (!authorizeService .isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ, - defaultPolicy.getID()) && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso)) { + defaultPolicy.getID()) && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) + && this.isNotCustomX(context, dso, defaultPolicy)) { ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy); newPolicy.setdSpaceObject(dso); newPolicy.setAction(Constants.READ); @@ -1070,15 +1072,35 @@ prevent the generation of resource policy entry values with null dspace_object a * @throws SQLException If something goes wrong retrieving the RP on the DSO */ private boolean isNotAlreadyACustomRPOfThisTypeOnDSO(Context context, DSpaceObject dso) throws SQLException { - List readRPs = resourcePolicyService.find(context, dso, Constants.READ); - for (ResourcePolicy readRP : readRPs) { - if (readRP.getRpType() != null && readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { - return false; + if (!configurationService.getBooleanProperty("core.authorization.installitem.inheritance-read.append-mode", + false)) { + List readRPs = resourcePolicyService.find(context, dso, Constants.READ); + for (ResourcePolicy readRP : readRPs) { + if (readRP.getRpType() != null && readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { + return false; + } } } return true; } + private boolean isNotCustomX(Context context, DSpaceObject dso, ResourcePolicy defaultPolicy) throws SQLException { + boolean hasCustomPolicy = resourcePolicyService.find(context, dso, Constants.READ) + .stream() + .filter(rp -> (Objects.nonNull(rp.getRpType()) && + Objects.equals(rp.getRpType(), ResourcePolicy.TYPE_CUSTOM))) + .findFirst() + .isPresent(); + + boolean isAnonimousGroup = Objects.nonNull(defaultPolicy.getGroup()) && + StringUtils.equals(defaultPolicy.getGroup().getName(),Group.ANONYMOUS) ? true : false; + + boolean datesAreNull = Objects.isNull(defaultPolicy.getStartDate()) && + Objects.isNull(defaultPolicy.getEndDate()) ? true : false; + + return !(hasCustomPolicy && isAnonimousGroup && datesAreNull); + } + /** * Returns an iterator of Items possessing the passed metadata field, or only * those matching the passed value, if value is not Item.ANY 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 c5fa04841c..cbe8e34bd6 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 @@ -5539,4 +5539,209 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()); } + @Test + public void patchUploadAddAdminRPInstallAndVerifyOnlyAdminCanViewTest() throws Exception { + configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", true); + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Col") + .build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withSubmitter(eperson) + .withTitle("Test wsItem") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "administrator"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + // verify that bitstream of workspace item has this admin RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator")))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); + + // verify that bitstream of workspace item still has this admin RP and no Anon READ inherited policy + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator")))) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.not(Matchers.hasItems( + ResourcePolicyMatcher + .matchResourcePolicyProperties(anonGroup, null, bitstream, null, Constants.READ, + null))))); + + // Bitstream should NOT be accessible to anon or eperson user, only to admin + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isUnauthorized()); + getClient(epersonToken).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isForbidden()); + getClient(adminToken).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + + @Test + public void patchUploadAddOpenAccessRPInstallAndVerifyAnonCanViewTest() throws Exception { + configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", true); + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection).withSubmitter(eperson) + .withTitle("Test wsItem") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "openaccess"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + + // verify that bitstream of workspace item has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess")))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // verify that bitstream of workspace item still has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess")))); + + // Bitstream should be accessible to anon + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + + @Test + public void patchUploadAddAdminThenOpenAccessRPInstallAndVerifyAnonCanViewTest() throws Exception { + configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", true); + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection).withSubmitter(eperson) + .withTitle("Test wsItem") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "administrator"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + // verify that bitstream of workspace item has this admin RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))); + + // prepare patch body + Map accessCondition2 = new HashMap<>(); + accessCondition2.put("name", "openaccess"); + List ops2 = new ArrayList<>(); + ops2.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition2)); + String patchBody2 = getPatchContent(ops2); + + // submit patch and verify response + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody2) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + // verify that bitstream of workspace item has this open access RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "openaccess") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // Bitstream should be accessible to anon + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + } + } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 1bd86b16f2..ef32dec9af 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -328,6 +328,7 @@ handle.dir = ${dspace.dir}/handle-server # ITEM ADMIN #core.authorization.item-admin.policies = true +#core.authorization.installitem.inheritance-read.append-mode = false # also bundle... #core.authorization.item-admin.create-bitstream = true #core.authorization.item-admin.delete-bitstream = true From c3eb1cfed6bec4aad9234131d7dd556350ecd78c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 25 Oct 2021 16:13:01 +0200 Subject: [PATCH 0442/1254] refactoring --- .../org/dspace/content/ItemServiceImpl.java | 41 +++-- .../rest/WorkspaceItemRestRepositoryIT.java | 153 +++++++++++++++++- 2 files changed, 176 insertions(+), 18 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 e1c6e21bd1..b93d33a559 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1047,12 +1047,14 @@ prevent the generation of resource policy entry values with null dspace_object a */ protected void addDefaultPoliciesNotInPlace(Context context, DSpaceObject dso, List defaultCollectionPolicies) throws SQLException, AuthorizeException { - + boolean appendMode = configurationService + .getBooleanProperty("core.authorization.installitem.inheritance-read.append-mode", false); for (ResourcePolicy defaultPolicy : defaultCollectionPolicies) { if (!authorizeService .isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ, - defaultPolicy.getID()) && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) - && this.isNotCustomX(context, dso, defaultPolicy)) { + defaultPolicy.getID()) && + ((!appendMode && this.isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso)) || + (appendMode && this.shouldBeAppended(context, dso, defaultPolicy)))) { ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy); newPolicy.setdSpaceObject(dso); newPolicy.setAction(Constants.READ); @@ -1072,19 +1074,28 @@ prevent the generation of resource policy entry values with null dspace_object a * @throws SQLException If something goes wrong retrieving the RP on the DSO */ private boolean isNotAlreadyACustomRPOfThisTypeOnDSO(Context context, DSpaceObject dso) throws SQLException { - if (!configurationService.getBooleanProperty("core.authorization.installitem.inheritance-read.append-mode", - false)) { - List readRPs = resourcePolicyService.find(context, dso, Constants.READ); - for (ResourcePolicy readRP : readRPs) { - if (readRP.getRpType() != null && readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { - return false; - } + List readRPs = resourcePolicyService.find(context, dso, Constants.READ); + for (ResourcePolicy readRP : readRPs) { + if (readRP.getRpType() != null && readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) { + return false; } } return true; } - private boolean isNotCustomX(Context context, DSpaceObject dso, ResourcePolicy defaultPolicy) throws SQLException { + /** + * Check if the provided default policy should be appended or not to the final + * item. If an item has at least one custom READ policy any anonymous READ + * policy with empty start/end date should be skipped + * + * @param context DSpace context + * @param dso DSpace object to check for custom read RP + * @param defaultPolicy The policy to check + * @return + * @throws SQLException If something goes wrong retrieving the RP on the DSO + */ + private boolean shouldBeAppended(Context context, DSpaceObject dso, ResourcePolicy defaultPolicy) + throws SQLException { boolean hasCustomPolicy = resourcePolicyService.find(context, dso, Constants.READ) .stream() .filter(rp -> (Objects.nonNull(rp.getRpType()) && @@ -1092,11 +1103,11 @@ prevent the generation of resource policy entry values with null dspace_object a .findFirst() .isPresent(); - boolean isAnonimousGroup = Objects.nonNull(defaultPolicy.getGroup()) && - StringUtils.equals(defaultPolicy.getGroup().getName(),Group.ANONYMOUS) ? true : false; + boolean isAnonimousGroup = Objects.nonNull(defaultPolicy.getGroup()) + && StringUtils.equals(defaultPolicy.getGroup().getName(), Group.ANONYMOUS); - boolean datesAreNull = Objects.isNull(defaultPolicy.getStartDate()) && - Objects.isNull(defaultPolicy.getEndDate()) ? true : false; + boolean datesAreNull = Objects.isNull(defaultPolicy.getStartDate()) + && Objects.isNull(defaultPolicy.getEndDate()); return !(hasCustomPolicy && isAnonimousGroup && datesAreNull); } 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 cbe8e34bd6..308337fd85 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 @@ -71,6 +71,7 @@ import org.dspace.content.Item; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -91,6 +92,8 @@ import org.springframework.test.web.servlet.MvcResult; */ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private CollectionService cs; @Autowired private ConfigurationService configurationService; @@ -5539,8 +5542,13 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()); } + /** + * this is the same test than + * {@link WorkspaceItemRestRepositoryIT#patchUploadAddAdminRPInstallAndVerifyOnlyAdminCanView()} + * except than it runs with append-mode enabled + */ @Test - public void patchUploadAddAdminRPInstallAndVerifyOnlyAdminCanViewTest() throws Exception { + public void patchUploadAddAdminRPInstallAndVerifyOnlyAdminCanViewInAppendModeTest() throws Exception { configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", true); context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context) @@ -5612,8 +5620,13 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()); } + /** + * this is the same test than + * {@link WorkspaceItemRestRepositoryIT#patchUploadAddOpenAccessRPInstallAndVerifyAnonCanView()} + * except than it runs with append-mode enabled + */ @Test - public void patchUploadAddOpenAccessRPInstallAndVerifyAnonCanViewTest() throws Exception { + public void patchUploadAddOpenAccessRPInstallAndVerifyAnonCanViewInAppendModeTest() throws Exception { configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", true); context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); @@ -5670,8 +5683,13 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()); } + /** + * this is the same test than + * {@link WorkspaceItemRestRepositoryIT#patchUploadAddAdminThenOpenAccessRPInstallAndVerifyAnonCanView()} + * except than it runs with append-mode enabled + */ @Test - public void patchUploadAddAdminThenOpenAccessRPInstallAndVerifyAnonCanViewTest() throws Exception { + public void patchUploadAddAdminThenOpenAccessRPInstallAndVerifyAnonCanViewInAppendModeTest() throws Exception { configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", true); context.turnOffAuthorisationSystem(); Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); @@ -5744,4 +5762,133 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()); } + @Test + public void patchUploadAddAdminmistratorRPAndVerifyCustomGroupCanViewInAppendModeTest() throws Exception { + configurationService.setProperty("core.authorization.installitem.inheritance-read.append-mode", true); + context.turnOffAuthorisationSystem(); + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("testMyUesr@userstest.net") + .withPassword(password) + .build(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + + Group customGroup = cs.createDefaultReadGroup(context, collection,"BITSTREAM",Constants.DEFAULT_BITSTREAM_READ); + groupService.addMember(context, customGroup, user); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection).withSubmitter(eperson) + .withTitle("Test wsItem") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + String userToken = getAuthToken(user.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "administrator"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + // verify that bitstream of workspace item has this admin RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // Bitstream should be accessible to user + getClient(userToken).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()); + + // Bitstream should be not accessible to anon + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void patchUploadAddAdminmistratorRPAndVerifyCustomGroupCannotViewTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("testMyUesr@userstest.net") + .withPassword(password) + .build(); + Community community = CommunityBuilder.createCommunity(context).withName("Com").build(); + Collection collection = CollectionBuilder.createCollection(context, community).withName("Col").build(); + + Group customGroup = cs.createDefaultReadGroup(context, collection,"BITSTREAM",Constants.DEFAULT_BITSTREAM_READ); + groupService.addMember(context, customGroup, user); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection).withSubmitter(eperson) + .withTitle("Test wsItem") + .withIssueDate("2019-03-06") + .withFulltext("upload2.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + context.restoreAuthSystemState(); + + String userToken = getAuthToken(user.getEmail(), password); + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "administrator"); + List ops = new ArrayList<>(); + ops.add(new AddOperation("/sections/upload/files/0/accessConditions/-", accessCondition)); + String patchBody = getPatchContent(ops); + + // submit patch and verify response + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + Bitstream bitstream = wItem.getItem().getBundles().get(0).getBitstreams().get(0); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + + // verify that bitstream of workspace item has this admin RP + getClient(adminToken).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.hasItems( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, bitstream, + ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator") + ))); + + // submit the workspaceitem to complete the deposit (as there is no workflow configured) + getClient(epersonToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // Bitstream should be accessible to user + getClient(userToken).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isForbidden()); + + // Bitstream should be not accessible to anon + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isUnauthorized()); + } + } From 7c10aeccbf4693be52033da9fc7ce57854766440 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 25 Oct 2021 16:19:27 +0200 Subject: [PATCH 0443/1254] minor fix --- .../java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3f3a8d6241..b3db2a74e2 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 @@ -71,8 +71,8 @@ import org.dspace.content.Item; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; -import org.dspace.content.service.ItemService; import org.dspace.content.service.CollectionService; +import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; From 0de9680781ea52a26f99be0656618bc073ca8dab Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 25 Oct 2021 16:55:24 +0200 Subject: [PATCH 0444/1254] Add tests against permission escalation --- .../app/rest/GroupRestRepositoryIT.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index f0cee34e40..70ba839378 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -53,6 +53,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.ResourcePolicyBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.factory.ContentServiceFactory; @@ -3025,4 +3026,129 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { } + @Test + public void commAdminAndColAdminCannotExploitItemReadGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(eperson) + .build(); + + Group adminGroup = groupService.findByName(context, Group.ADMIN); + ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.DEFAULT_ITEM_READ) + .withGroup(adminGroup).withDspaceObject(child1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.DEFAULT_ITEM_READ) + .withGroup(adminGroup).withDspaceObject(col1).build(); + context.restoreAuthSystemState(); + + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + String tokenAdminCol = getAuthToken(adminChild1.getEmail(), password); + + assertFalse(groupService.isMember(context, adminChild1, adminGroup)); + assertFalse(groupService.isMember(context, adminCol1, adminGroup)); + + getClient(tokenAdminCol) + .perform(post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + adminCol1.getID())) + .andExpect(status().isForbidden()); + + assertFalse(groupService.isMember(context, adminCol1, adminGroup)); + + getClient(tokenAdminComm) + .perform(post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + adminChild1.getID())) + .andExpect(status().isForbidden()); + + assertFalse(groupService.isMember(context, adminChild1, adminGroup)); + + } + + @Test + public void commAdminAndColAdminCannotExpoloitBitstreamReadGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(eperson) + .build(); + + Group adminGroup = groupService.findByName(context, Group.ADMIN); + ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.DEFAULT_BITSTREAM_READ) + .withGroup(adminGroup).withDspaceObject(child1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withAction(Constants.DEFAULT_BITSTREAM_READ) + .withGroup(adminGroup).withDspaceObject(col1).build(); + context.restoreAuthSystemState(); + + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + String tokenAdminCol = getAuthToken(adminChild1.getEmail(), password); + + assertFalse(groupService.isMember(context, adminChild1, adminGroup)); + assertFalse(groupService.isMember(context, adminCol1, adminGroup)); + + getClient(tokenAdminCol) + .perform(post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + adminCol1.getID())) + .andExpect(status().isForbidden()); + + assertFalse(groupService.isMember(context, adminCol1, adminGroup)); + + getClient(tokenAdminComm) + .perform(post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + adminChild1.getID())) + .andExpect(status().isForbidden()); + + assertFalse(groupService.isMember(context, adminChild1, adminGroup)); + } + } From 277b499a5cd3a4f5eb2370513a1b7e4ec2a6e041 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 25 Oct 2021 19:01:36 +0200 Subject: [PATCH 0445/1254] Fix for GHSA-cf2j-vf36-c6w8 --- .../dspace/content/CollectionServiceImpl.java | 9 +++++++-- .../content/service/CollectionService.java | 10 ++++++++++ .../org/dspace/eperson/GroupServiceImpl.java | 20 +++++++++++++++---- 3 files changed, 33 insertions(+), 6 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 58085ef0d8..de29b8026a 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -920,8 +920,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i int defaultRead) throws SQLException, AuthorizeException { Group role = groupService.create(context); - groupService.setName(role, "COLLECTION_" + collection.getID().toString() + "_" + typeOfGroupString + - "_DEFAULT_READ"); + groupService.setName(role, getDefaultReadGroupName(collection, typeOfGroupString)); // Remove existing privileges from the anonymous group. authorizeService.removePoliciesActionFilter(context, collection, defaultRead); @@ -932,6 +931,12 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i return role; } + @Override + public String getDefaultReadGroupName(Collection collection, String typeOfGroupString) { + return "COLLECTION_" + collection.getID().toString() + "_" + typeOfGroupString + + "_DEFAULT_READ"; + } + @Override public List findCollectionsWithSubmit(String q, Context context, Community community, int offset, int limit) throws SQLException, SearchServiceException { 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 3a48065795..83bf9cf57a 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 @@ -360,6 +360,16 @@ public interface CollectionService Group createDefaultReadGroup(Context context, Collection collection, String typeOfGroupString, int defaultRead) throws SQLException, AuthorizeException; + /** + * This method will return the name to give to the group created by the + * {@link #createDefaultReadGroup(Context, Collection, String, int)} method + * + * @param collection The DSpace collection to use in the name generation + * @param typeOfGroupString The type of group to use in the name generation + * @return the name to give to the group that hold default read for the collection + */ + String getDefaultReadGroupName(Collection collection, String typeOfGroupString); + /** * Returns Collections for which the current user has 'submit' privileges. * NOTE: for better performance, this method retrieves its results from an 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 4c98ed20c8..be81cd9bd8 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -15,6 +15,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -735,13 +736,24 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements groups.add(group); List policies = resourcePolicyService.find(context, null, groups, Constants.DEFAULT_ITEM_READ, Constants.COLLECTION); - if (policies.size() > 0) { - return policies.get(0).getdSpaceObject(); + + Optional defaultPolicy = policies.stream().filter(p -> StringUtils.equals( + collectionService.getDefaultReadGroupName((Collection) p.getdSpaceObject(), "ITEM"), + group.getName())).findFirst(); + + if (defaultPolicy.isPresent()) { + return defaultPolicy.get().getdSpaceObject(); } policies = resourcePolicyService.find(context, null, groups, Constants.DEFAULT_BITSTREAM_READ, Constants.COLLECTION); - if (policies.size() > 0) { - return policies.get(0).getdSpaceObject(); + + defaultPolicy = policies.stream() + .filter(p -> StringUtils.equals(collectionService.getDefaultReadGroupName( + (Collection) p.getdSpaceObject(), "BITSTREAM"), group.getName())) + .findFirst(); + + if (defaultPolicy.isPresent()) { + return defaultPolicy.get().getdSpaceObject(); } } } From 16cf19bed05617af3f92d7ed7b809f4b6196e094 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 25 Oct 2021 19:03:57 +0200 Subject: [PATCH 0446/1254] IT to proof that DSpace/DSpace#7928 is solved --- .../content/service/CollectionService.java | 2 +- .../app/rest/GroupRestRepositoryIT.java | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) 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 83bf9cf57a..522bdac224 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 @@ -363,7 +363,7 @@ public interface CollectionService /** * This method will return the name to give to the group created by the * {@link #createDefaultReadGroup(Context, Collection, String, int)} method - * + * * @param collection The DSpace collection to use in the name generation * @param typeOfGroupString The type of group to use in the name generation * @return the name to give to the group that hold default read for the collection diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index 70ba839378..7121e11953 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -3151,4 +3151,39 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { assertFalse(groupService.isMember(context, adminChild1, adminGroup)); } + @Test + /** + * Test for bug https://github.com/DSpace/DSpace/issues/7928 + * @throws Exception + */ + public void anonymousGroupParentObjectTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/eperson/groups/" + anonGroup.getID().toString()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", GroupMatcher.matchFullEmbeds())) + .andExpect(jsonPath("$", GroupMatcher.matchLinks(anonGroup.getID()))) + .andExpect(jsonPath("$", Matchers.is( + GroupMatcher.matchGroupEntry(anonGroup.getID(), anonGroup.getName()) + ))) + .andExpect(jsonPath("$._embedded.object").doesNotExist()) + ; + } + } From d3ba3b6c71c62c144b6a5251986fdd2f55cba858 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 25 Oct 2021 21:30:39 +0200 Subject: [PATCH 0447/1254] fix failed test --- .../app/rest/EntityTypeRestRepositoryIT.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 75df09607c..93aacb60e4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -6,7 +6,6 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -30,7 +29,6 @@ import org.dspace.core.Constants; import org.dspace.external.provider.AbstractExternalDataProvider; import org.dspace.external.service.ExternalDataService; import org.hamcrest.Matchers; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -354,8 +352,6 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$.page.number", is(1))); } - // TEST IS TEMPORARILY BROKEN ON MAIN. REQUIRES FIXING - @Ignore @Test public void findAllByAuthorizedExternalSource() throws Exception { context.turnOffAuthorisationSystem(); @@ -419,18 +415,22 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("pubmed")) .setSupportedEntityTypes(Arrays.asList("Publication")); + // these are similar to the previous checks but now we have restricted the mock and pubmed providers + // to support only publication, this mean that there are no providers suitable for funding getClient(token).perform(get("/api/core/entitytypes/search/findAllByAuthorizedExternalSource")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.entitytypes", contains( - EntityTypeMatcher.matchEntityTypeEntry(publication)))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(publication), + EntityTypeMatcher.matchEntityTypeEntry(project)))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))); getClient(adminToken).perform(get("/api/core/entitytypes/search/findAllByAuthorizedExternalSource")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntry(project), EntityTypeMatcher.matchEntityTypeEntry(orgUnit), EntityTypeMatcher.matchEntityTypeEntry(publication)))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); } finally { ((AbstractExternalDataProvider) externalDataService.getExternalDataProvider("mock")) From f025e81713999cbf4bf560f9f66bcbdf3245625a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 28 Oct 2021 10:26:53 -0500 Subject: [PATCH 0448/1254] Update LICENSES_THIRD_PARTY for v7.1 --- LICENSES_THIRD_PARTY | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 40e0d5ea19..c0cfe62a78 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -83,8 +83,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) * Apache Commons Logging (commons-logging:commons-logging:1.2 - http://commons.apache.org/proper/commons-logging/) * Apache Commons Validator (commons-validator:commons-validator:1.5.0 - http://commons.apache.org/proper/commons-validator/) + * GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson) * Boilerpipe -- Boilerplate Removal and Fulltext Extraction from HTML pages (de.l3s.boilerpipe:boilerpipe:1.1.0 - http://code.google.com/p/boilerpipe/) * SentimentAnalysisParser (edu.usc.ir:sentiment-analysis-parser:0.1 - https://github.com/USCDataScience/SentimentAnalysisParser) + * OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu) * Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core) * Graphite Integration for Metrics (io.dropwizard.metrics:metrics-graphite:4.1.5 - https://metrics.dropwizard.io/metrics-graphite) * Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9) @@ -104,6 +106,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * OpenTracing-util (io.opentracing:opentracing-util:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-util) * Google S2 geometry library (io.sgr:s2-geometry-library-java:1.0.0 - https://github.com/sgr-io/s2-geometry-library-java) * Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:2.0.2 - https://beanvalidation.org) + * JSR107 API and SPI (javax.cache:cache-api:1.1.0 - https://github.com/jsr107/jsr107spec) * javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/) * Bean Validation API (javax.validation:validation-api:2.0.1.Final - http://beanvalidation.org) * jdbm (jdbm:jdbm:1.0 - no url defined) @@ -118,12 +121,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JSON Small and Fast Parser (net.minidev:json-smart:2.3 - http://www.minidev.net/) * ehcache (net.sf.ehcache:ehcache:2.10.6 - http://ehcache.org) * Ehcache Core (net.sf.ehcache:ehcache-core:2.6.11 - http://ehcache.org) - * Ehcache 3 (org.ehcache:ehcache:3.4.0 - https://www.ehcache.org/) - * cache-config (javax.cache:cache-api:1.1.0 - https://mvnrepository.com/artifact/javax.cache/cache-api) * Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core) * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) - * Apache Ant Core (org.apache.ant:ant:1.10.9 - https://ant.apache.org/) - * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.9 - https://ant.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) * Calcite Core (org.apache.calcite:calcite-core:1.18.0 - https://calcite.apache.org/calcite-core) * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.18.0 - https://calcite.apache.org/calcite-linq4j) @@ -241,6 +242,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jose4j (org.bitbucket.b_c:jose4j:0.6.5 - https://bitbucket.org/b_c/jose4j/) * TagSoup (org.ccil.cowan.tagsoup:tagsoup:1.2.1 - http://home.ccil.org/~cowan/XML/tagsoup/) * Woodstox (org.codehaus.woodstox:woodstox-core-asl:4.4.1 - http://woodstox.codehaus.org) + * jems (org.dmfs:jems:1.18 - https://github.com/dmfs/jems) + * rfc3986-uri (org.dmfs:rfc3986-uri:0.8.1 - https://github.com/dmfs/uri-toolkit) * Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty) * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/) * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.34.v20201102 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client) @@ -270,7 +273,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.34.v20201102 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport) * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.34.v20201102 - https://eclipse.org/jetty/http2-parent/http2-server) * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas) - * flyway-core (org.flywaydb:flyway-core:6.5.5 - https://flywaydb.org/flyway-core) + * Ehcache (org.ehcache:ehcache:3.4.0 - http://ehcache.org) + * flyway-core (org.flywaydb:flyway-core:6.5.7 - https://flywaydb.org/flyway-core) * Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava) * Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava) * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) @@ -320,12 +324,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Spring Boot Configuration Processor (org.springframework.boot:spring-boot-configuration-processor:2.0.0.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-tools/spring-boot-configuration-processor) * Spring Boot Starter (org.springframework.boot:spring-boot-starter:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter) * Spring Boot AOP Starter (org.springframework.boot:spring-boot-starter-aop:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-aop) + * Spring Boot Cache Starter (org.springframework.boot:spring-boot-starter-cache:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-cache) * Spring Boot Data REST Starter (org.springframework.boot:spring-boot-starter-data-rest:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-data-rest) * Spring Boot Json Starter (org.springframework.boot:spring-boot-starter-json:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-json) * Spring Boot Log4j 2 Starter (org.springframework.boot:spring-boot-starter-log4j2:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-log4j2) * Spring Boot Security Starter (org.springframework.boot:spring-boot-starter-security:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-security) * Spring Boot Test Starter (org.springframework.boot:spring-boot-starter-test:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-test) - * Spring Boot Cache Starter (org.springframework.boot:spring-boot-starter-cache::2.2.6.RELEASE - https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache) * Spring Boot Tomcat Starter (org.springframework.boot:spring-boot-starter-tomcat:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-tomcat) * Spring Boot Validation Starter (org.springframework.boot:spring-boot-starter-validation:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-validation) * Spring Boot Web Starter (org.springframework.boot:spring-boot-starter-web:2.2.6.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-web) @@ -372,6 +376,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * janino (org.codehaus.janino:janino:3.0.9 - http://janino-compiler.github.io/janino/) * Stax2 API (org.codehaus.woodstox:stax2-api:3.1.4 - http://wiki.fasterxml.com/WoodstoxStax2) * dom4j (org.dom4j:dom4j:2.1.1 - http://dom4j.github.io/) + * Hamcrest Date (org.exparity:hamcrest-date:2.0.7 - https://github.com/exparity/hamcrest-date) * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Hamcrest (org.hamcrest:hamcrest:2.1 - http://hamcrest.org/JavaHamcrest/) @@ -384,7 +389,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 - JDBC 4.2 (org.postgresql:postgresql:42.2.9 - https://github.com/pgjdbc/pgjdbc) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.2.24 - https://jdbc.postgresql.org) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) @@ -392,6 +397,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JavaBeans Activation Framework (com.sun.activation:javax.activation:1.2.0 - http://java.net/all/javax.activation/) * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.7 - http://java.net/istack-commons/istack-commons-runtime/) + * Old JAXB Core (com.sun.xml.bind:jaxb-core:2.3.0.1 - http://jaxb.java.net/jaxb-bundles/jaxb-core) + * Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:2.3.1 - http://jaxb.java.net/jaxb-bundles/jaxb-impl) * saaj-impl (com.sun.xml.messaging.saaj:saaj-impl:1.4.0-b03 - http://java.net/saaj-impl/) * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api) @@ -438,6 +445,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Eclipse Public License: + * System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/) * c3p0 (com.mchange:c3p0:0.9.5.5 - https://github.com/swaldman/c3p0) * mchange-commons-java (com.mchange:mchange-commons-java:0.2.19 - https://github.com/swaldman/mchange-commons-java) * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca) @@ -522,8 +530,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines MIT License: - * iiif-apis (de.digitalcollections.iiif:iiif-apis:0.3.9 - https://github.com/dbmdz/iiif-apis) * Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver) + * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.9 - https://github.com/dbmdz/iiif-apis) * CDM core library (edu.ucar:cdm:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/documentation.htm) * GRIB IOSP and Feature Collection (edu.ucar:grib:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/) * HttpClient Wrappers (edu.ucar:httpservices:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/documentation.htm) @@ -564,12 +572,17 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) + * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * XZ for Java (org.tukaani:xz:1.8 - https://tukaani.org/xz/java.html) The JSON License: * JSON in Java (org.json:json:20180130 - https://github.com/douglascrockford/JSON-java) + The New BSD License: + + * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) + UnRar License: * Java UnRar (com.github.junrar:junrar:4.0.0 - https://github.com/junrar/junrar) From c2e31d08ef10b4cd2e3dd1853f18d1f06c7dbe25 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 28 Oct 2021 13:48:56 -0500 Subject: [PATCH 0449/1254] [maven-release-plugin] prepare release dspace-7.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 375b843be1..55b942ee3d 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index ffe8f36a1e..af2c7bbbc5 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index b4609c6ea7..9ee43e7d3a 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 16528cd1ad..b8f1411a9a 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index aaeb6ec690..dedafbfb6f 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.1-SNAPSHOT + 7.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.1-SNAPSHOT + 7.1 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 4d85e867fc..982b2d468a 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index d7c0a4d36e..7c929b743a 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 38576fb363..e217c05766 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 8543e10752..5985d6dbb4 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index b7a7c65048..e6190be661 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 925a7c1e32..3fdbad4ddc 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 65edaaecad..044d089440 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.1-SNAPSHOT + 7.1 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 01cdf2c940..5280c2108c 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.1-SNAPSHOT + 7.1 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 0a26771dcc..41ea32baa2 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.1-SNAPSHOT + 7.1 ../pom.xml diff --git a/pom.xml b/pom.xml index 86345a4c9b..ffe09416cd 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.1-SNAPSHOT + 7.1 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -857,14 +857,14 @@ org.dspace dspace-rest - 7.1-SNAPSHOT + 7.1 jar classes org.dspace dspace-rest - 7.1-SNAPSHOT + 7.1 war @@ -1011,69 +1011,69 @@ org.dspace dspace-api - 7.1-SNAPSHOT + 7.1 org.dspace dspace-api test-jar - 7.1-SNAPSHOT + 7.1 test org.dspace.modules additions - 7.1-SNAPSHOT + 7.1 org.dspace dspace-sword - 7.1-SNAPSHOT + 7.1 org.dspace dspace-swordv2 - 7.1-SNAPSHOT + 7.1 org.dspace dspace-oai - 7.1-SNAPSHOT + 7.1 org.dspace dspace-services - 7.1-SNAPSHOT + 7.1 org.dspace dspace-server-webapp test-jar - 7.1-SNAPSHOT + 7.1 test org.dspace dspace-rdf - 7.1-SNAPSHOT + 7.1 org.dspace dspace-iiif - 7.1-SNAPSHOT + 7.1 org.dspace dspace-server-webapp - 7.1-SNAPSHOT + 7.1 jar classes org.dspace dspace-server-webapp - 7.1-SNAPSHOT + 7.1 war @@ -1855,7 +1855,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.1 From e297d9809592d63248cceb0155d51143ee535e46 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 28 Oct 2021 13:49:02 -0500 Subject: [PATCH 0450/1254] [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 55b942ee3d..abced08999 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index af2c7bbbc5..a0e123dcd0 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 9ee43e7d3a..17315ee3b5 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index b8f1411a9a..4207edc760 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index dedafbfb6f..103c1b5e1d 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.1 + 7.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.1 + 7.2-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 982b2d468a..24959edd93 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 7c929b743a..654250e140 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index e217c05766..9745b595eb 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 5985d6dbb4..4068853198 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index e6190be661..c4d0776052 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 3fdbad4ddc..73d58d6876 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 044d089440..39b457fd9a 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.1 + 7.2-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 5280c2108c..30095ca578 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.1 + 7.2-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 41ea32baa2..a4912ab169 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.1 + 7.2-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index ffe09416cd..08702a4a09 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.1 + 7.2-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -857,14 +857,14 @@ org.dspace dspace-rest - 7.1 + 7.2-SNAPSHOT jar classes org.dspace dspace-rest - 7.1 + 7.2-SNAPSHOT war @@ -1011,69 +1011,69 @@ org.dspace dspace-api - 7.1 + 7.2-SNAPSHOT org.dspace dspace-api test-jar - 7.1 + 7.2-SNAPSHOT test org.dspace.modules additions - 7.1 + 7.2-SNAPSHOT org.dspace dspace-sword - 7.1 + 7.2-SNAPSHOT org.dspace dspace-swordv2 - 7.1 + 7.2-SNAPSHOT org.dspace dspace-oai - 7.1 + 7.2-SNAPSHOT org.dspace dspace-services - 7.1 + 7.2-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.1 + 7.2-SNAPSHOT test org.dspace dspace-rdf - 7.1 + 7.2-SNAPSHOT org.dspace dspace-iiif - 7.1 + 7.2-SNAPSHOT org.dspace dspace-server-webapp - 7.1 + 7.2-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.1 + 7.2-SNAPSHOT war @@ -1855,7 +1855,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.1 + HEAD From b242d200555f72d95cb48e24da180d317bb0f06b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 4 Nov 2021 16:45:30 -0500 Subject: [PATCH 0451/1254] Fix IT by ensuring it calls super.destroy() --- .../src/test/java/org/dspace/app/packager/PackagerIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index bf392dccec..ecc63af9f6 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -14,7 +14,6 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; -import java.sql.SQLException; import java.util.Iterator; import java.util.UUID; import java.util.zip.ZipEntry; @@ -86,8 +85,9 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { } @After - public void destroy() throws SQLException, IOException { + public void destroy() throws Exception { tempFile.delete(); + super.destroy(); } @Test From b990d75d9e7defa2f58ca0189497cd306bd26f5d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 5 Nov 2021 10:19:25 -0500 Subject: [PATCH 0452/1254] Fix a recently broken IT by including "none" entity type --- .../app/rest/RelationshipTypeRestControllerIT.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java index eae949caed..241025e2d4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java @@ -49,7 +49,8 @@ public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationT .andExpect(status().isOk()) .andExpect(jsonPath("$.page", - is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 7)))) + is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 8)))) + // Expect it to return these specific Entity Types (in any order) .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( EntityTypeMatcher.matchEntityTypeEntryForLabel("Publication"), EntityTypeMatcher.matchEntityTypeEntryForLabel("Person"), @@ -57,8 +58,10 @@ public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationT EntityTypeMatcher.matchEntityTypeEntryForLabel("OrgUnit"), EntityTypeMatcher.matchEntityTypeEntryForLabel("Journal"), EntityTypeMatcher.matchEntityTypeEntryForLabel("JournalVolume"), - EntityTypeMatcher.matchEntityTypeEntryForLabel("JournalIssue") - + EntityTypeMatcher.matchEntityTypeEntryForLabel("JournalIssue"), + // None is the "empty" entity type used for allowing Collections / External Sources to work with + // non-Entities (i.e. normal items) + EntityTypeMatcher.matchEntityTypeEntryForLabel("none") ))) ; } From 4c23431d40fa99f581d125f92f6eb39791614aa4 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 5 Nov 2021 11:07:49 -0500 Subject: [PATCH 0453/1254] Add missing @Override --- dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index ecc63af9f6..c814d2d9f6 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -85,6 +85,7 @@ public class PackagerIT extends AbstractIntegrationTestWithDatabase { } @After + @Override public void destroy() throws Exception { tempFile.delete(); super.destroy(); From b1b08b21c6f8b869b03bb0680bf8a1259e419057 Mon Sep 17 00:00:00 2001 From: astrowq Date: Wed, 3 Nov 2021 20:24:29 +0100 Subject: [PATCH 0454/1254] fix the date parsing error with the format "yyyy-mm-dd" --- dspace-api/src/main/java/org/dspace/core/Utils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index 1e2a2d20c7..df6359b141 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -87,7 +87,9 @@ public final class Utils { // finally, try without any timezone (defaults to current TZ) new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS"), - new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss"), + + new SimpleDateFormat("yyyy'-'MM'-'dd") }; // for formatISO8601Date @@ -334,7 +336,7 @@ public final class Utils { char tzSign = s.charAt(s.length() - 6); if (s.endsWith("Z")) { s = s.substring(0, s.length() - 1) + "GMT+00:00"; - } else if (tzSign == '-' || tzSign == '+') { + } else if ((tzSign == '-' || tzSign == '+') && s.length() > 10) { // check for trailing timezone s = s.substring(0, s.length() - 6) + "GMT" + s.substring(s.length() - 6); } From 13eb4a4587a61a7ea4ce30b65a85e16231ad88ef Mon Sep 17 00:00:00 2001 From: astrowq Date: Wed, 3 Nov 2021 20:28:20 +0100 Subject: [PATCH 0455/1254] simple code refactoring --- .../src/main/java/org/dspace/core/Utils.java | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index df6359b141..b9fff20c76 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -77,7 +77,7 @@ public final class Utils { private static final VMID vmid = new VMID(); // for parseISO8601Date - private static final SimpleDateFormat parseFmt[] = { + private static final SimpleDateFormat[] parseFmt = { // first try at parsing, has milliseconds (note General time zone) new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSz"), @@ -161,11 +161,11 @@ public final class Utils { StringBuilder result = new StringBuilder(); // This is far from the most efficient way to do things... - for (int i = 0; i < data.length; i++) { - int low = (int) (data[i] & 0x0F); - int high = (int) (data[i] & 0xF0); + for (byte datum : data) { + int low = datum & 0x0F; + int high = datum & 0xF0; - result.append(Integer.toHexString(high).substring(0, 1)); + result.append(Integer.toHexString(high).charAt(0)); result.append(Integer.toHexString(low)); } @@ -201,13 +201,7 @@ public final class Utils { byte[] junk = new byte[16]; random.nextBytes(junk); - - String input = new StringBuilder() - .append(vmid) - .append(new java.util.Date()) - .append(Arrays.toString(junk)) - .append(counter++) - .toString(); + String input = String.valueOf(vmid) + new Date() + Arrays.toString(junk) + counter++; return getMD5Bytes(input.getBytes(StandardCharsets.UTF_8)); } @@ -296,7 +290,7 @@ public final class Utils { } String units = m.group(2); - long multiplier = MS_IN_SECOND; + long multiplier; if ("s".equals(units)) { multiplier = MS_IN_SECOND; @@ -343,9 +337,9 @@ public final class Utils { // try to parse without milliseconds ParseException lastError = null; - for (int i = 0; i < parseFmt.length; ++i) { + for (SimpleDateFormat simpleDateFormat : parseFmt) { try { - return parseFmt[i].parse(s); + return simpleDateFormat.parse(s); } catch (ParseException e) { lastError = e; } @@ -378,7 +372,7 @@ public final class Utils { } public static java.util.Collection emptyIfNull(java.util.Collection collection) { - return collection == null ? Collections.emptyList() : collection; + return collection == null ? Collections.emptyList() : collection; } /** @@ -459,7 +453,7 @@ public final class Utils { if (hostname != null) { return hostname.startsWith("www.") ? hostname.substring(4) : hostname; } - return hostname; + return null; } catch (URISyntaxException e) { return null; } From d61e98cf328c03127603a78e993b5bfac8a03172 Mon Sep 17 00:00:00 2001 From: jose vicente ribelles aguilar Date: Tue, 9 Nov 2021 10:22:14 +0100 Subject: [PATCH 0456/1254] Update V7.0_2019.07.31__Retrieval_of_name_variant.sql Specify length. Use VARCHAR2 instead of VARCHAR --- .../oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql index 57f00a993f..4d61de791a 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql @@ -11,8 +11,8 @@ -- Rename columns left_label and right_label to leftward_type and rightward_type ----------------------------------------------------------------------------------- -ALTER TABLE relationship ADD leftward_value VARCHAR; -ALTER TABLE relationship ADD rightward_value VARCHAR; +ALTER TABLE relationship ADD leftward_value VARCHAR2(50); +ALTER TABLE relationship ADD rightward_value VARCHAR2(50); ALTER TABLE relationship_type RENAME left_label TO leftward_type; -ALTER TABLE relationship_type RENAME right_label TO rightward_type; \ No newline at end of file +ALTER TABLE relationship_type RENAME right_label TO rightward_type; From e0fc4fe345c8456c8b386d005166887b1d2f46bf Mon Sep 17 00:00:00 2001 From: jose vicente ribelles aguilar Date: Tue, 9 Nov 2021 10:26:50 +0100 Subject: [PATCH 0457/1254] Update V7.0_2018.04.16__dspace-entities.sql no type uuid. Change uuid for raw(16) --- .../oracle/V7.0_2018.04.16__dspace-entities.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql index 68855dc2dc..4255e1859d 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql @@ -47,9 +47,9 @@ CREATE TABLE relationship_type CREATE TABLE relationship ( id INTEGER NOT NULL PRIMARY KEY, - left_id uuid NOT NULL REFERENCES item(uuid), + left_id raw(16) NOT NULL REFERENCES item(uuid), type_id INTEGER NOT NULL REFERENCES relationship_type(id), - right_id uuid NOT NULL REFERENCES item(uuid), + right_id raw(16) NOT NULL REFERENCES item(uuid), left_place INTEGER, right_place INTEGER, CONSTRAINT u_constraint UNIQUE (left_id, type_id, right_id) @@ -62,4 +62,4 @@ CREATE INDEX relationship_type_by_right_type_idx ON relationship_type(right_type CREATE INDEX relationship_type_by_left_label_idx ON relationship_type(left_label); CREATE INDEX relationship_type_by_right_label_idx ON relationship_type(right_label); CREATE INDEX relationship_by_left_id_idx ON relationship(left_id); -CREATE INDEX relationship_by_right_id_idx ON relationship(right_id); \ No newline at end of file +CREATE INDEX relationship_by_right_id_idx ON relationship(right_id); From 0361ed59fe8f2a7136487a70f33c6b0661075a00 Mon Sep 17 00:00:00 2001 From: jose vicente ribelles aguilar Date: Tue, 9 Nov 2021 10:32:36 +0100 Subject: [PATCH 0458/1254] Update V7.0_2018.04.16__dspace-entities.sql shorten identifiers to less than 30 characters --- .../oracle/V7.0_2018.04.16__dspace-entities.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql index 4255e1859d..fc1c0b2e23 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql @@ -57,9 +57,9 @@ CREATE TABLE relationship ); CREATE INDEX entity_type_label_idx ON entity_type(label); -CREATE INDEX relationship_type_by_left_type_idx ON relationship_type(left_type); -CREATE INDEX relationship_type_by_right_type_idx ON relationship_type(right_type); -CREATE INDEX relationship_type_by_left_label_idx ON relationship_type(left_label); -CREATE INDEX relationship_type_by_right_label_idx ON relationship_type(right_label); +CREATE INDEX rl_ty_by_left_type_idx ON relationship_type(left_type); +CREATE INDEX rl_ty_by_right_type_idx ON relationship_type(right_type); +CREATE INDEX rl_ty_by_left_label_idx ON relationship_type(left_label); +CREATE INDEX rl_ty_by_right_label_idx ON relationship_type(right_label); CREATE INDEX relationship_by_left_id_idx ON relationship(left_id); CREATE INDEX relationship_by_right_id_idx ON relationship(right_id); From e3ea3626cb7ccc1469783c67c89fd310ddfe7f7e Mon Sep 17 00:00:00 2001 From: jose vicente ribelles aguilar Date: Tue, 9 Nov 2021 11:35:41 +0100 Subject: [PATCH 0459/1254] Update V7.0_2019.11.13__relationship_type_copy_left_right.sql Error Message: ORA-00984: column not allowed here. No BOOLEAN type in ORACLE. --- .../V7.0_2019.11.13__relationship_type_copy_left_right.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql index a58bbc501d..0db294c1c1 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.11.13__relationship_type_copy_left_right.sql @@ -10,5 +10,5 @@ -- Create columns copy_left and copy_right for RelationshipType ----------------------------------------------------------------------------------- -ALTER TABLE relationship_type ADD copy_to_left BOOLEAN DEFAULT FALSE NOT NULL; -ALTER TABLE relationship_type ADD copy_to_right BOOLEAN DEFAULT FALSE NOT NULL; \ No newline at end of file +ALTER TABLE relationship_type ADD copy_to_left NUMBER(1) DEFAULT 0 NOT NULL; +ALTER TABLE relationship_type ADD copy_to_right NUMBER(1) DEFAULT 0 NOT NULL; From a4a00a8949429766e8d4ef58366f813c5526da1f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 10 Nov 2021 16:31:07 -0600 Subject: [PATCH 0460/1254] Refactor DSpaceAuthentication object to make it clearer when user is authenticated and when they are not. Fix a few usages in code --- .../rest/security/DSpaceAuthentication.java | 36 +++++++++++++++---- .../ShibbolethAuthenticationFilter.java | 5 ++- .../StatelessAuthenticationFilter.java | 4 +-- .../rest/security/StatelessLoginFilter.java | 7 ++-- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceAuthentication.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceAuthentication.java index 16246dee9b..99378c9169 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceAuthentication.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/DSpaceAuthentication.java @@ -28,23 +28,47 @@ public class DSpaceAuthentication implements Authentication { private String username; private String password; private List authorities; - private boolean authenticated = true; - + private boolean authenticated; + /** + * Create a DSpaceAuthentication instance for an already authenticated EPerson, including their GrantedAuthority + * objects. + *

    + * NOTE: This type of DSpaceAuthentication object is returned to Spring after a successful authentication. + * @param ePerson authenticated EPerson + * @param authorities EPerson's authorities + */ public DSpaceAuthentication(EPerson ePerson, List authorities) { this.previousLoginDate = ePerson.getPreviousActive(); this.username = ePerson.getEmail(); this.authorities = authorities; + this.authenticated = true; } - public DSpaceAuthentication(String username, String password, List authorities) { + /** + * Create a temporary DSpaceAuthentication instance which may be used to store information about the user who will + * be attempting authentication. + *

    + * NOTE: This type of DSpaceAuthentication object is used to attempt a new authentication in DSpace. It is therefore + * temporary in nature, as it will be discarded after successful authentication. + * @param username username to attempt authentication for + * @param password password to use for authentication + */ + public DSpaceAuthentication(String username, String password) { this.username = username; this.password = password; - this.authorities = authorities; + this.authenticated = false; } - public DSpaceAuthentication(String username, List authorities) { - this(username, null, authorities); + /** + * Create a temporary, empty DSpaceAuthentication instance which may be used to trigger an implicit authentication. + * An example is Shibboleth, as this doesn't require an explicit username/password, as the user will have been + * authenticated externally, and DSpace just needs to perform an implicit authentication by looking for the auth + * data passed to it by Shibboleth. + */ + public DSpaceAuthentication() { + // Initialize with a 'null' username and password + this(null, (String) null); } public Collection getAuthorities() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java index b6a36d00e9..3d3edcf07c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java @@ -50,9 +50,8 @@ public class ShibbolethAuthenticationFilter extends StatelessLoginFilter { // has already happened in Shibboleth & we are just intercepting the return request in order to check // for a valid Shibboleth login (using ShibAuthentication.authenticate()) & save current user to Context // See org.dspace.app.rest.ShibbolethRestController JavaDocs for an outline of the entire Shib login process. - return authenticationManager.authenticate( - new DSpaceAuthentication(null, null, new ArrayList<>()) - ); + // NOTE: because this authentication is implicit, we pass in an empty DSpaceAuthentication + return authenticationManager.authenticate(new DSpaceAuthentication()); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java index 1afedf517c..7309f579a6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java @@ -142,7 +142,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { } //Return the Spring authentication object - return new DSpaceAuthentication(eperson.getEmail(), authorities); + return new DSpaceAuthentication(eperson, authorities); } else { return null; } @@ -174,7 +174,7 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { if (!authorizeService.isAdmin(context, onBehalfOfEPerson)) { requestService.setCurrentUserId(epersonUuid); context.switchContextUser(onBehalfOfEPerson); - return new DSpaceAuthentication(onBehalfOfEPerson.getEmail(), + return new DSpaceAuthentication(onBehalfOfEPerson, authenticationProvider.getGrantedAuthorities(context)); } else { throw new IllegalArgumentException("You're unable to use the login as feature to log " + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java index 466687fc68..8a84c5b2cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.security; import java.io.IOException; -import java.util.ArrayList; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -70,9 +69,9 @@ public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter String password = req.getParameter("password"); // Attempt to authenticate by passing user & password (if provided) to AuthenticationProvider class(es) - return authenticationManager.authenticate( - new DSpaceAuthentication(user, password, new ArrayList<>()) - ); + // NOTE: This method will check if the user was already authenticated by StatelessAuthenticationFilter, + // and, if so, just refresh their token. + return authenticationManager.authenticate(new DSpaceAuthentication(user, password)); } /** From a4eda3010305a05bc3c0a997af187a5ecd59c4ae Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 10 Nov 2021 16:32:41 -0600 Subject: [PATCH 0461/1254] Remove unused parameters from several auth related methods. --- .../EPersonRestAuthenticationProvider.java | 46 +++++++++++++++++-- .../ShibbolethAuthenticationFilter.java | 1 - .../rest/security/jwt/JWTTokenHandler.java | 6 +-- ...JWTTokenRestAuthenticationServiceImpl.java | 10 +--- .../rest/AuthenticationRestControllerIT.java | 6 +-- .../security/jwt/JWTTokenHandlerTest.java | 10 ++-- .../jwt/ShortLivedJWTTokenHandlerTest.java | 11 ++--- 7 files changed, 59 insertions(+), 31 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java index 040e9f9c9c..95e504ad3b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java @@ -65,18 +65,37 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { Context context = ContextUtil.obtainContext(request); + // If a user already exists in the context, then no authentication is necessary. User is already logged in if (context != null && context.getCurrentUser() != null) { + // Simply refresh/reload the auth token. If token has expired, the token will change. return authenticateRefreshTokenRequest(context); } else { + // Otherwise, this is a new login & we need to attempt authentication return authenticateNewLogin(authentication); } } + /** + * Trigger a JWT token refresh by updating the currently logged-in user's "lastActive" date to *now*. + * Since the logged-in user's "lastActive" date is used to determine whether the token has expired, this *may* + * cause the token to change (if expiration time has passed). If expiration has not passed, this request will + * return the same token as before. + * @param context current DSpace context (for currently logged in user information) + * @return DSpaceAuthentication object representing authenticated user + */ private Authentication authenticateRefreshTokenRequest(Context context) { authenticationService.updateLastActiveDate(context); - return createAuthentication(null, context); + return createAuthentication(context); } + /** + * Attempt a new login to DSpace based on the information provided in the Authentication class. + * If login is successful, returns a NEW Authentication class containing the logged in EPerson and their list of + * GrantedAuthority objects. If login fails, a BadCredentialsException is thrown. If no valid login found implicit + * or explicit, then null is returned. + * @param authentication Authentication class to attempt authentication. + * @return new Authentication class containing logged-in user information or null + */ private Authentication authenticateNewLogin(Authentication authentication) { Context newContext = null; Authentication output = null; @@ -91,7 +110,7 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider if (implicitStatus == AuthenticationMethod.SUCCESS) { log.info(LogHelper.getHeader(newContext, "login", "type=implicit")); - output = createAuthentication(password, newContext); + output = createAuthentication(newContext); } else { int authenticateResult = authenticationService .authenticate(newContext, name, password, null, request); @@ -100,7 +119,7 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider log.info(LogHelper .getHeader(newContext, "login", "type=explicit")); - output = createAuthentication(password, newContext); + output = createAuthentication(newContext); } else { log.info(LogHelper.getHeader(newContext, "failed_login", "email=" + name + ", result=" @@ -122,7 +141,15 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider return output; } - private Authentication createAuthentication(final String password, final Context context) { + /** + * Create a valid Spring Authentication object for the user currently authenticated in the Context. + * If no current user is found in the Context, then the login must have failed and a BadCredentialsException is + * thrown. + * @param context current DSpace context + * @return DSpaceAuthentication object for currently authenticated user + * @throws BadCredentialsException if no current user found + */ + private Authentication createAuthentication(final Context context) { EPerson ePerson = context.getCurrentUser(); if (ePerson != null && StringUtils.isNotBlank(ePerson.getEmail())) { @@ -137,6 +164,11 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider } } + /** + * Return list of GrantedAuthority objects for the user currently authenticated in the Context + * @param context current DSpace context + * @return List of GrantedAuthority. Empty list is returned if no current user exists. + */ public List getGrantedAuthorities(Context context) { List authorities = new LinkedList<>(); EPerson eperson = context.getCurrentUser(); @@ -165,6 +197,12 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider return authorities; } + /** + * Return whether this provider supports this Authentication type. Only returns true if the Authentication type + * is a valid DSpaceAuthentication class. + * @param authentication + * @return true if valid DSpaceAuthentication class + */ @Override public boolean supports(Class authentication) { return DSpaceAuthentication.class.isAssignableFrom(authentication); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java index 3d3edcf07c..05f347ef7b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.security; import java.io.IOException; -import java.util.ArrayList; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index 46dbfea9dc..91da4b0ea1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -36,7 +36,6 @@ import org.apache.commons.lang3.StringUtils; 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.service.ClientInfoService; import org.dspace.services.ConfigurationService; @@ -155,12 +154,11 @@ public abstract class JWTTokenHandler { * @param context current Context * @param request current Request * @param previousLoginDate date of last login (before this one) - * @param groups List of user Groups * @return string version of signed JWT * @throws JOSEException */ - public String createTokenForEPerson(Context context, HttpServletRequest request, Date previousLoginDate, - List groups) throws JOSEException, SQLException { + public String createTokenForEPerson(Context context, HttpServletRequest request, Date previousLoginDate) + throws JOSEException, SQLException { // Verify that the user isn't trying to use a short lived token to generate another token if (StringUtils.isNotBlank(request.getParameter(AUTHORIZATION_TOKEN_PARAMETER))) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java index c7012c7568..5c685f61af 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenRestAuthenticationServiceImpl.java @@ -11,7 +11,6 @@ import java.io.IOException; import java.sql.SQLException; import java.text.ParseException; import java.util.Iterator; -import java.util.List; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -28,7 +27,6 @@ import org.dspace.authenticate.AuthenticationMethod; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; import org.dspace.eperson.service.EPersonService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,10 +80,8 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication Context context = ContextUtil.obtainContext(request); context.setCurrentUser(ePersonService.findByEmail(context, authentication.getName())); - List groups = authenticationService.getSpecialGroups(context, request); - String token = loginJWTTokenHandler.createTokenForEPerson(context, request, - authentication.getPreviousLoginDate(), groups); + authentication.getPreviousLoginDate()); context.commit(); // Add newly generated auth token to the response @@ -107,9 +103,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication @Override public AuthenticationToken getShortLivedAuthenticationToken(Context context, HttpServletRequest request) { try { - String token; - List groups = authenticationService.getSpecialGroups(context, request); - token = shortLivedJWTTokenHandler.createTokenForEPerson(context, request, null, groups); + String token = shortLivedJWTTokenHandler.createTokenForEPerson(context, request, null); context.commit(); return new AuthenticationToken(token); } catch (JOSEException e) { 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 5584fbface..48d81483cb 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 @@ -506,7 +506,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(cookie().value("DSPACE-XSRF-COOKIE", "")) // CSRF Tokens generated by Spring Security are UUIDs .andExpect(header().string("DSPACE-XSRF-TOKEN", matchesPattern(REGEX_UUID))) - .andReturn().getResponse().getHeader("Authorization"); + .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER); assertNotEquals(token, newToken); @@ -517,8 +517,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticated", is(true))) .andExpect(jsonPath("$.type", is("status"))); - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) + // Logout, invalidating new token + getClient(newToken).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java index 3b0eb84793..66bb68ad81 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/JWTTokenHandlerTest.java @@ -92,7 +92,7 @@ public class JWTTokenHandlerTest { public void testJWTNoEncryption() throws Exception { Date previous = new Date(System.currentTimeMillis() - 10000000000L); String token = loginJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); SignedJWT signedJWT = SignedJWT.parse(token); String personId = (String) signedJWT.getJWTClaimsSet().getClaim(EPersonClaimProvider.EPERSON_ID); assertEquals("epersonID", personId); @@ -105,7 +105,7 @@ public class JWTTokenHandlerTest { StringKeyGenerator keyGenerator = KeyGenerators.string(); when(configurationService.getProperty("jwt.login.encryption.secret")).thenReturn(keyGenerator.generateKey()); String token = loginJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); SignedJWT signedJWT = SignedJWT.parse(token); } @@ -116,7 +116,7 @@ public class JWTTokenHandlerTest { when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); String token = loginJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); EPerson parsed = loginJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); assertEquals(null, parsed); @@ -129,7 +129,7 @@ public class JWTTokenHandlerTest { when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); String token = loginJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().claim("eid", "epersonID").expirationTime( new Date(System.currentTimeMillis() + 99999999)).build(); String tamperedPayload = new String(Base64.getUrlEncoder().encode(jwtClaimsSet.toString().getBytes())); @@ -144,7 +144,7 @@ public class JWTTokenHandlerTest { Date previous = new Date(System.currentTimeMillis() - 10000000000L); // create a new token String token = loginJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); // immediately invalidate it loginJWTTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context); // Check if it is still valid by trying to parse the EPerson from it (should return null) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java index 795694b202..7049721865 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/jwt/ShortLivedJWTTokenHandlerTest.java @@ -12,7 +12,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import java.text.ParseException; -import java.util.ArrayList; import java.util.Base64; import java.util.Date; import javax.servlet.http.HttpServletRequest; @@ -56,7 +55,7 @@ public class ShortLivedJWTTokenHandlerTest extends JWTTokenHandlerTest { public void testJWTNoEncryption() throws Exception { Date previous = new Date(System.currentTimeMillis() - 10000000000L); String token = shortLivedJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); SignedJWT signedJWT = SignedJWT.parse(token); String personId = (String) signedJWT.getJWTClaimsSet().getClaim(EPersonClaimProvider.EPERSON_ID); assertEquals("epersonID", personId); @@ -70,7 +69,7 @@ public class ShortLivedJWTTokenHandlerTest extends JWTTokenHandlerTest { when(configurationService.getProperty("jwt.shortLived.encryption.secret")) .thenReturn(keyGenerator.generateKey()); String token = shortLivedJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); SignedJWT signedJWT = SignedJWT.parse(token); } @@ -82,7 +81,7 @@ public class ShortLivedJWTTokenHandlerTest extends JWTTokenHandlerTest { when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); String token = shortLivedJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); EPerson parsed = shortLivedJWTTokenHandler.parseEPersonFromToken(token, httpServletRequest, context); assertEquals(null, parsed); @@ -95,7 +94,7 @@ public class ShortLivedJWTTokenHandlerTest extends JWTTokenHandlerTest { when(ePersonClaimProvider.getEPerson(any(Context.class), any(JWTClaimsSet.class))).thenReturn(ePerson); Date previous = new Date(new Date().getTime() - 10000000000L); String token = shortLivedJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().claim("eid", "epersonID").expirationTime( new Date(System.currentTimeMillis() + 99999999)).build(); String tamperedPayload = new String(Base64.getUrlEncoder().encode(jwtClaimsSet.toString().getBytes())); @@ -110,7 +109,7 @@ public class ShortLivedJWTTokenHandlerTest extends JWTTokenHandlerTest { Date previous = new Date(System.currentTimeMillis() - 10000000000L); // create a new token String token = shortLivedJWTTokenHandler - .createTokenForEPerson(context, new MockHttpServletRequest(), previous, new ArrayList<>()); + .createTokenForEPerson(context, new MockHttpServletRequest(), previous); // immediately invalidate it shortLivedJWTTokenHandler.invalidateToken(token, new MockHttpServletRequest(), context); // Check if it is still valid by trying to parse the EPerson from it (should return null) From 21549d0198fb82164655aa69b12eb3fd83824365 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 8 Nov 2021 14:06:06 -0600 Subject: [PATCH 0462/1254] Rename ShibbolethAuthenticationFilter to ShibbolethLoginFilter --- .../org/dspace/app/rest/ShibbolethRestController.java | 5 +++-- ...henticationFilter.java => ShibbolethLoginFilter.java} | 9 ++++++--- .../app/rest/security/WebSecurityConfiguration.java | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/security/{ShibbolethAuthenticationFilter.java => ShibbolethLoginFilter.java} (89%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ShibbolethRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ShibbolethRestController.java index f00967961c..8c8bd11d3b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ShibbolethRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ShibbolethRestController.java @@ -14,6 +14,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.AuthnRest; +import org.dspace.app.rest.security.ShibbolethLoginFilter; import org.dspace.authenticate.ShibAuthentication; import org.dspace.core.Utils; import org.dspace.services.ConfigurationService; @@ -38,7 +39,7 @@ import org.springframework.web.bind.annotation.RestController; * 3. User logs in using Shibboleth * 4. If successful, they are redirected by Shibboleth to this Controller (the path of this controller is passed * to Shibboleth as a URL param in step 1) - * 5. NOTE: Prior to hitting this Controller, {@link org.dspace.app.rest.security.ShibbolethAuthenticationFilter} + * 5. NOTE: Prior to hitting this Controller, {@link ShibbolethLoginFilter} * briefly intercepts the request in order to check for a valid Shibboleth login (see * ShibAuthentication.authenticate()) and store that user info in a JWT. * 6. This Controller then gets the request & looks for a "redirectUrl" param (also a part of the original URL from @@ -48,7 +49,7 @@ import org.springframework.web.bind.annotation.RestController; * @author Andrea Bollini (andrea dot bollini at 4science dot it) * @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it) * @see ShibAuthentication - * @see org.dspace.app.rest.security.ShibbolethAuthenticationFilter + * @see ShibbolethLoginFilter */ @RequestMapping(value = "/api/" + AuthnRest.CATEGORY + "/shibboleth") @RestController diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java similarity index 89% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java index 05f347ef7b..519f09d57c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java @@ -13,6 +13,8 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authenticate.ShibAuthentication; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderNotFoundException; @@ -29,10 +31,11 @@ import org.springframework.security.core.AuthenticationException; * @see org.dspace.app.rest.ShibbolethRestController * @see org.dspace.authenticate.ShibAuthentication */ -public class ShibbolethAuthenticationFilter extends StatelessLoginFilter { +public class ShibbolethLoginFilter extends StatelessLoginFilter { + private static final Logger log = LogManager.getLogger(ShibbolethLoginFilter.class); - public ShibbolethAuthenticationFilter(String url, AuthenticationManager authenticationManager, - RestAuthenticationService restAuthenticationService) { + public ShibbolethLoginFilter(String url, AuthenticationManager authenticationManager, + RestAuthenticationService restAuthenticationService) { super(url, authenticationManager, restAuthenticationService); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index 8ec70dfd34..bf729b5433 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -134,8 +134,8 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { LogoutFilter.class) // Add a filter before our shibboleth endpoints to do the authentication based on the data in the // HTTP request - .addFilterBefore(new ShibbolethAuthenticationFilter("/api/authn/shibboleth", authenticationManager(), - restAuthenticationService), + .addFilterBefore(new ShibbolethLoginFilter("/api/authn/shibboleth", authenticationManager(), + restAuthenticationService), LogoutFilter.class) // Add a custom Token based authentication filter based on the token previously given to the client // before each URL From e01ec94881861b760046b19bb12e7a9125fb9982 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 10 Nov 2021 17:11:13 -0600 Subject: [PATCH 0463/1254] Remove ShibbolethRestController and move code into ShibbolethLoginFilter. --- .../authenticate/ShibAuthentication.java | 2 +- .../app/rest/ShibbolethRestController.java | 107 ------------------ .../rest/security/ShibbolethLoginFilter.java | 83 ++++++++++++-- .../rest/AuthenticationRestControllerIT.java | 4 +- .../ShibbolethLoginFilterIT.java} | 6 +- 5 files changed, 78 insertions(+), 124 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/ShibbolethRestController.java rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{ShibbolethRestControllerIT.java => security/ShibbolethLoginFilterIT.java} (96%) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java index 53502a22ce..9e8e7c1069 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java @@ -515,7 +515,7 @@ public class ShibAuthentication implements AuthenticationMethod { } // Determine the server return URL, where shib will send the user after authenticating. - // We need it to go back to DSpace's ShibbolethRestController so we will extract the user's information, + // We need it to trigger DSpace's ShibbolethLoginFilter so we will extract the user's information, // locally authenticate them & then redirect back to the UI. String returnURL = configurationService.getProperty("dspace.server.url") + "/api/authn/shibboleth" + ((redirectUrl != null) ? "?redirectUrl=" + redirectUrl : ""); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ShibbolethRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ShibbolethRestController.java deleted file mode 100644 index 8c8bd11d3b..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ShibbolethRestController.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.model.AuthnRest; -import org.dspace.app.rest.security.ShibbolethLoginFilter; -import org.dspace.authenticate.ShibAuthentication; -import org.dspace.core.Utils; -import org.dspace.services.ConfigurationService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.hateoas.Link; -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; - -/** - * Rest controller that handles redirect *after* shibboleth authentication succeeded. - *

    - * Shibboleth authentication does NOT occur in this Controller, but occurs before this class is called. - * The general Shibboleth login process is as follows: - * 1. When Shibboleth plugin is enabled, client/UI receives Shibboleth's absolute URL in WWW-Authenticate header. - * See {@link org.dspace.authenticate.ShibAuthentication} loginPageURL() method. - * 2. Client sends the user to that URL when they select Shibboleth authentication. - * 3. User logs in using Shibboleth - * 4. If successful, they are redirected by Shibboleth to this Controller (the path of this controller is passed - * to Shibboleth as a URL param in step 1) - * 5. NOTE: Prior to hitting this Controller, {@link ShibbolethLoginFilter} - * briefly intercepts the request in order to check for a valid Shibboleth login (see - * ShibAuthentication.authenticate()) and store that user info in a JWT. - * 6. This Controller then gets the request & looks for a "redirectUrl" param (also a part of the original URL from - * step 1), and redirects the user to that location (after verifying it's a trusted URL). Usually this is a - * redirect back to the Client/UI page where the User started. - * - * @author Andrea Bollini (andrea dot bollini at 4science dot it) - * @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it) - * @see ShibAuthentication - * @see ShibbolethLoginFilter - */ -@RequestMapping(value = "/api/" + AuthnRest.CATEGORY + "/shibboleth") -@RestController -public class ShibbolethRestController implements InitializingBean { - - private static final Logger log = LoggerFactory.getLogger(ShibbolethRestController.class); - - @Autowired - ConfigurationService configurationService; - - @Autowired - DiscoverableEndpointsService discoverableEndpointsService; - - @Override - public void afterPropertiesSet() { - discoverableEndpointsService - .register(this, Arrays.asList(new Link("/api/" + AuthnRest.CATEGORY, "shibboleth"))); - } - - // LGTM.com thinks this method has an unvalidated URL redirect (https://lgtm.com/rules/4840088/) in `redirectUrl`, - // even though we are clearly validating the hostname of `redirectUrl` and test it in ShibbolethRestControllerIT - @SuppressWarnings("lgtm[java/unvalidated-url-redirection]") - @RequestMapping(method = RequestMethod.GET) - public void shibboleth(HttpServletResponse response, - @RequestParam(name = "redirectUrl", required = false) String redirectUrl) throws IOException { - // NOTE: By the time we get here, we already know that Shibboleth is enabled & authentication succeeded, - // as both of those are verified by ShibbolethAuthenticationFilter which runs before this controller - - // If redirectUrl unspecified, default to the configured UI - if (StringUtils.isEmpty(redirectUrl)) { - redirectUrl = configurationService.getProperty("dspace.ui.url"); - } - - // Validate that the redirectURL matches either the server or UI hostname. It *cannot* be an arbitrary URL. - String redirectHostName = Utils.getHostName(redirectUrl); - String serverHostName = Utils.getHostName(configurationService.getProperty("dspace.server.url")); - ArrayList allowedHostNames = new ArrayList(); - allowedHostNames.add(serverHostName); - String[] allowedUrls = configurationService.getArrayProperty("rest.cors.allowed-origins"); - for (String url : allowedUrls) { - allowedHostNames.add(Utils.getHostName(url)); - } - - if (StringUtils.equalsAnyIgnoreCase(redirectHostName, allowedHostNames.toArray(new String[0]))) { - log.debug("Shibboleth redirecting to " + redirectUrl); - response.sendRedirect(redirectUrl); - } else { - log.error("Invalid Shibboleth redirectURL=" + redirectUrl + - ". URL doesn't match hostname of server or UI!"); - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - "Invalid redirectURL! Must match server or ui hostname."); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java index 519f09d57c..9c78f9e3ad 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java @@ -8,14 +8,19 @@ package org.dspace.app.rest.security; import java.io.IOException; +import java.util.ArrayList; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authenticate.ShibAuthentication; +import org.dspace.core.Utils; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.core.Authentication; @@ -24,16 +29,33 @@ import org.springframework.security.core.AuthenticationException; /** * This class will filter Shibboleth requests to see if the user has been authenticated via Shibboleth. *

    - * This filter runs before the ShibbolethRestController, in order to verify Shibboleth authentication succeeded, - * and create the authentication token (JWT). + * The overall Shibboleth login process is as follows: + * 1. When Shibboleth plugin is enabled, client/UI receives Shibboleth's absolute URL in WWW-Authenticate header. + * See {@link org.dspace.authenticate.ShibAuthentication} loginPageURL() method. + * 2. Client sends the user to that URL when they select Shibboleth authentication. + * 3. User logs in using Shibboleth + * 4. If successful, they are redirected by Shibboleth to the path where this Filter is "listening" (that path + * is passed to Shibboleth as a URL param in step 1) + * 5. This filter then intercepts the request in order to check for a valid Shibboleth login (see + * ShibAuthentication.authenticate()) and stores that user info in a JWT. It also saves that JWT in a *temporary* + * authentication cookie. + * 6. This filter then looks for a "redirectUrl" param (also a part of the original URL from step 1), and redirects + * the user to that location (after verifying it's a trusted URL). Usually this is a redirect back to the + * Client/UI page where the User started. + * 7. At that point, the client reads the JWT from the Cookie, and sends it back in a request to /api/authn/login, + * which triggers the server-side to destroy the Cookie and move the JWT into a Header + *

    + * This Shibboleth Authentication process is tested in AuthenticationRestControllerIT. * * @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it) - * @see org.dspace.app.rest.ShibbolethRestController + * @author Tim Donohue * @see org.dspace.authenticate.ShibAuthentication */ public class ShibbolethLoginFilter extends StatelessLoginFilter { private static final Logger log = LogManager.getLogger(ShibbolethLoginFilter.class); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + public ShibbolethLoginFilter(String url, AuthenticationManager authenticationManager, RestAuthenticationService restAuthenticationService) { super(url, authenticationManager, restAuthenticationService); @@ -49,9 +71,9 @@ public class ShibbolethLoginFilter extends StatelessLoginFilter { } // In the case of Shibboleth, this method does NOT actually authenticate us. The authentication - // has already happened in Shibboleth & we are just intercepting the return request in order to check - // for a valid Shibboleth login (using ShibAuthentication.authenticate()) & save current user to Context - // See org.dspace.app.rest.ShibbolethRestController JavaDocs for an outline of the entire Shib login process. + // has already happened in Shibboleth. So, this call to "authenticate()" is just triggering + // ShibAuthentication.authenticate() to check for a valid Shibboleth login, and if found, the current user + // is considered authenticated via Shibboleth. // NOTE: because this authentication is implicit, we pass in an empty DSpaceAuthentication return authenticationManager.authenticate(new DSpaceAuthentication()); } @@ -65,12 +87,51 @@ public class ShibbolethLoginFilter extends StatelessLoginFilter { DSpaceAuthentication dSpaceAuthentication = (DSpaceAuthentication) auth; // OVERRIDE DEFAULT behavior of StatelessLoginFilter to return a temporary authentication cookie containing - // the Auth Token (JWT). This Cookie is required because ShibbolethRestController *redirects* the user - // back to the client/UI after a successful Shibboleth login. Headers cannot be sent via a redirect, so a Cookie - // must be sent to provide the auth token to the client. On the next request from the client, the cookie is - // read and destroyed & the Auth token is only used in the Header from that point forward. + // the Auth Token (JWT). This Cookie is required because we *redirect* the user back to the client/UI after + // a successful Shibboleth login. Headers cannot be sent via a redirect, so a Cookie must be sent to provide + // the auth token to the client. On the next request from the client, the cookie is read and destroyed & the + // Auth token is only used in the Header from that point forward. restAuthenticationService.addAuthenticationDataForUser(req, res, dSpaceAuthentication, true); - chain.doFilter(req, res); + + // redirect user after completing Shibboleth authentication, sending along the temporary auth cookie + redirectAfterSuccess(req, res); } + + /** + * After successful login, redirect to the DSpace URL specified by this Shibboleth request (in the "redirectUrl" + * request parameter). If that 'redirectUrl' is not valid or trusted for this DSpace site, then return a 400 error. + * @param request + * @param response + * @throws IOException + */ + private void redirectAfterSuccess(HttpServletRequest request, HttpServletResponse response) throws IOException { + // Get redirect URL from request parameter + String redirectUrl = request.getParameter("redirectUrl"); + + // If redirectUrl unspecified, default to the configured UI + if (StringUtils.isEmpty(redirectUrl)) { + redirectUrl = configurationService.getProperty("dspace.ui.url"); + } + + // Validate that the redirectURL matches either the server or UI hostname. It *cannot* be an arbitrary URL. + String redirectHostName = Utils.getHostName(redirectUrl); + String serverHostName = Utils.getHostName(configurationService.getProperty("dspace.server.url")); + ArrayList allowedHostNames = new ArrayList<>(); + allowedHostNames.add(serverHostName); + String[] allowedUrls = configurationService.getArrayProperty("rest.cors.allowed-origins"); + for (String url : allowedUrls) { + allowedHostNames.add(Utils.getHostName(url)); + } + + if (StringUtils.equalsAnyIgnoreCase(redirectHostName, allowedHostNames.toArray(new String[0]))) { + log.debug("Shibboleth redirecting to " + redirectUrl); + response.sendRedirect(redirectUrl); + } else { + log.error("Invalid Shibboleth redirectURL=" + redirectUrl + + ". URL doesn't match hostname of server or UI!"); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, + "Invalid redirectURL! Must match server or ui hostname."); + } + } } 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 48d81483cb..7584874668 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 @@ -173,7 +173,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // In order to fully simulate a Shibboleth authentication, we'll call // /api/authn/shibboleth?redirectUrl=[UI-URL] , with valid Shibboleth request attributes. - // In this situation, we are mocking how Shibboleth works from our UI (see also ShibbolethRestController): + // In this situation, we are mocking how Shibboleth works from our UI (see also ShibbolethLoginFilter): // (1) The UI sends the user to Shibboleth to login // (2) After a successful login, Shibboleth redirects user to /api/authn/shibboleth?redirectUrl=[url] // (3) That triggers generation of the auth token (JWT), and redirects the user to 'redirectUrl', sending along @@ -181,7 +181,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // In below call, we're sending a GET request (as that's what a redirect is), with a Referer of a "fake" // Shibboleth server to simulate this request coming back from Shibboleth (after a successful login). // We are then verifying the user will be redirected to the 'redirectUrl' with a single-use auth cookie - // (NOTE: Additional tests of this /api/authn/shibboleth endpoint can be found in ShibbolethRestControllerIT) + // (NOTE: Additional tests of this /api/authn/shibboleth endpoint can be found in ShibbolethLoginFilterIT) Cookie authCookie = getClient().perform(get("/api/authn/shibboleth") .header("Referer", "https://myshib.example.com") .param("redirectUrl", uiURL) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ShibbolethLoginFilterIT.java similarity index 96% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ShibbolethLoginFilterIT.java index 4d3e99c449..29dcc68d4e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ShibbolethLoginFilterIT.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.rest; +package org.dspace.app.rest.security; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; @@ -18,11 +18,11 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; /** - * Integration test that cover ShibbolethRestController + * Integration tests that cover ShibbolethLoginFilter behavior (especially around redirects) * * @author Giuseppe Digilio (giuseppe dot digilio at 4science dot it) */ -public class ShibbolethRestControllerIT extends AbstractControllerIntegrationTest { +public class ShibbolethLoginFilterIT extends AbstractControllerIntegrationTest { @Autowired ConfigurationService configurationService; From d2430193b13eb1a241bd9e3e752d3a6e3fd6592f Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 22 Nov 2021 18:02:47 +0100 Subject: [PATCH 0464/1254] Improved InitializeEntitiesIT destroy method to not delete none entity type --- .../test/java/org/dspace/app/rest/InitializeEntitiesIT.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java index b7be1e9eb2..4daf681982 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java @@ -25,6 +25,7 @@ import org.dspace.content.RelationshipType; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Constants; import org.dspace.services.ConfigurationService; import org.junit.After; import org.junit.Before; @@ -83,7 +84,9 @@ public class InitializeEntitiesIT extends AbstractControllerIntegrationTest { } for (EntityType entityType: entityTypeList) { - entityTypeService.delete(context, entityType); + if (!Constants.ENTITY_TYPE_NONE.equals(entityType.getLabel())) { + entityTypeService.delete(context, entityType); + } } context.restoreAuthSystemState(); From b7f07da8a2b05977025ed2cf40ed09362594353f Mon Sep 17 00:00:00 2001 From: pablo Date: Fri, 19 Nov 2021 10:02:22 -0300 Subject: [PATCH 0465/1254] Fix condition for metadata validation if is Authority Controlled --- .../app/rest/submit/step/validation/MetadataValidation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java index 55fc667a6d..f3a9935dd6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java @@ -81,7 +81,7 @@ public class MetadataValidation extends AbstractValidation { if (isAuthorityControlled) { String authKey = md.getAuthority(); if (metadataAuthorityService.isAuthorityRequired(fieldKey) && - StringUtils.isNotBlank(authKey)) { + StringUtils.isBlank(authKey)) { addError(ERROR_VALIDATION_AUTHORITY_REQUIRED, "/" + WorkspaceItemRestRepository.OPERATION_PATH_SECTIONS + "/" + config.getId() + "/" + input.getFieldName() + "/" + md.getPlace()); From 1db8e382d574234d0444ab90b4a02d34917ec12f Mon Sep 17 00:00:00 2001 From: William Welling Date: Tue, 23 Nov 2021 11:04:51 -0600 Subject: [PATCH 0466/1254] chown app directory during dependency images build (#8035) --- Dockerfile.dependencies | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index 54647ebad1..a05c1dfb5d 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -12,6 +12,9 @@ WORKDIR /app RUN useradd dspace \ && mkdir /home/dspace \ && chown -Rv dspace: /home/dspace + +RUN chown -Rv dspace: /app + USER dspace # Copy the DSpace source code into the workdir (excluding .dockerignore contents) From 070b61181451f007822a120dc948f655670e40a8 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 4 Oct 2021 13:58:17 -0400 Subject: [PATCH 0467/1254] First attempt at a single pool of HTTP connections for Solr. --- .../authority/AuthoritySolrServiceImpl.java | 9 +- .../org/dspace/discovery/SolrSearchCore.java | 9 +- .../impl/HttpConnectionPoolService.java | 144 ++++++++++++++++++ .../dspace/statistics/SolrStatisticsCore.java | 10 +- .../impl/solr/DSpaceSolrServerResolver.java | 8 +- .../dspace/xoai/solr/DSpaceSolrServer.java | 9 +- 6 files changed, 182 insertions(+), 7 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java index 8d9ab0c5fc..860630e8c2 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -22,6 +23,7 @@ import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrInputDocument; import org.dspace.authority.indexer.AuthorityIndexingService; +import org.dspace.service.impl.HttpConnectionPoolService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -35,6 +37,9 @@ public class AuthoritySolrServiceImpl implements AuthorityIndexingService, Autho private static final Logger log = LogManager.getLogger(AuthoritySolrServiceImpl.class); + @Inject + private HttpConnectionPoolService httpConnectionPoolService; + protected AuthoritySolrServiceImpl() { } @@ -54,7 +59,9 @@ public class AuthoritySolrServiceImpl implements AuthorityIndexingService, Autho log.debug("Solr authority URL: " + solrService); - HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); + HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService) + .withHttpClient(httpConnectionPoolService.getClient()) + .build(); solrServer.setBaseURL(solrService); SolrQuery solrQuery = new SolrQuery().setQuery("*:*"); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java index 47288ece34..dac719695e 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java @@ -18,13 +18,14 @@ import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.dspace.discovery.indexobject.IndexableItem; +import org.dspace.service.impl.HttpConnectionPoolService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.storage.rdbms.DatabaseUtils; import org.springframework.beans.factory.annotation.Autowired; /** - * Bean containing the SolrClient for the search core + * Bean containing the SolrClient for the search core. * @author Kevin Van de Velde (kevin at atmire dot com) */ public class SolrSearchCore { @@ -34,6 +35,8 @@ public class SolrSearchCore { protected IndexingService indexingService; @Autowired protected ConfigurationService configurationService; + @Autowired + protected HttpConnectionPoolService httpConnectionPoolService; /** * SolrServer for processing indexing events. @@ -79,7 +82,9 @@ public class SolrSearchCore { .getBooleanProperty("discovery.solr.url.validation.enabled", true)) { try { log.debug("Solr URL: " + solrService); - HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService).build(); + HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService) + .withHttpClient(httpConnectionPoolService.getClient()) + .build(); solrServer.setBaseURL(solrService); solrServer.setUseMultiPartPost(true); diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java new file mode 100644 index 0000000000..0eeb93a441 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java @@ -0,0 +1,144 @@ +package org.dspace.service.impl; + +import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.http.HeaderElement; +import org.apache.http.HeaderElementIterator; +import org.apache.http.HttpResponse; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicHeaderElementIterator; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.dspace.services.ConfigurationService; + +/** + * Factory for HTTP clients sharing a pool of connections. + * + * @author Mark H. Wood + */ +@Named +@Singleton +public class HttpConnectionPoolService { + @Inject + private ConfigurationService configurationService; + + private final PoolingHttpClientConnectionManager connManager; + + private final Thread connectionMonitor; + + private final ConnectionKeepAliveStrategy keepAliveStrategy; + + private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 10; + + private static final int DEFAULT_MAX_PER_ROUTE = 6; + + private static final int DEFAULT_KEEPALIVE = 5 * 1000; // milliseconds + + private static final int CHECK_INTERVAL = 1000; // milliseconds + + private static final int IDLE_INTERVAL = 30; // seconds + + public HttpConnectionPoolService() { + connManager = new PoolingHttpClientConnectionManager(); + connManager.setMaxTotal(configurationService.getIntProperty( + "solrClient.maxTotalConnections", DEFAULT_MAX_TOTAL_CONNECTIONS)); + connManager.setDefaultMaxPerRoute( + configurationService.getIntProperty("solrClient.maxPerRoute", + DEFAULT_MAX_PER_ROUTE)); + + connectionMonitor = new IdleConnectionMonitorThread(connManager); + connectionMonitor.setDaemon(true); + + keepAliveStrategy = new KeepAliveStrategy(); + } + + @PostConstruct + protected void init() { + connectionMonitor.start(); + } + + /** + * Create an HTTP client which uses a pooled connection. + * + * @return the client. + */ + public CloseableHttpClient getClient() { + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setKeepAliveStrategy(keepAliveStrategy) + .setConnectionManager(connManager) + .build(); + return httpClient; + } + + /** + * A connection keep-alive strategy that obeys the Keep-Alive header and + * applies a default if none is given. + * + * Swiped from https://www.baeldung.com/httpclient-connection-management + */ + public class KeepAliveStrategy + implements ConnectionKeepAliveStrategy { + @Override + public long getKeepAliveDuration(HttpResponse response, + HttpContext context) { + HeaderElementIterator it = new BasicHeaderElementIterator( + response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + while (it.hasNext()) { + HeaderElement he = it.nextElement(); + String name = he.getName(); + String value = he.getValue(); + if (value != null && "timeout".equalsIgnoreCase(name)) { + return Long.parseLong(value) * 1000; + } + } + + return configurationService.getIntProperty("solrClient.keepAlive", + DEFAULT_KEEPALIVE); + } + } + + /** + * Clean up stale connections. + * + * Swiped from https://www.baeldung.com/httpclient-connection-management + */ + public class IdleConnectionMonitorThread + extends Thread { + private final HttpClientConnectionManager connMgr; + private volatile boolean shutdown; + + public IdleConnectionMonitorThread( + PoolingHttpClientConnectionManager connMgr) { + super(); + this.connMgr = connMgr; + } + @Override + public void run() { + try { + while (!shutdown) { + synchronized (this) { + wait(CHECK_INTERVAL); + connMgr.closeExpiredConnections(); + connMgr.closeIdleConnections(IDLE_INTERVAL, TimeUnit.SECONDS); + } + } + } catch (InterruptedException ex) { + shutdown(); + } + } + public void shutdown() { + shutdown = true; + synchronized (this) { + notifyAll(); + } + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java b/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java index 345084ef6b..9d60913e98 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java @@ -12,6 +12,7 @@ import static org.apache.logging.log4j.LogManager.getLogger; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.dspace.service.impl.HttpConnectionPoolService; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -20,13 +21,16 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class SolrStatisticsCore { - private static Logger log = getLogger(SolrStatisticsCore.class); + private static final Logger log = getLogger(); protected SolrClient solr = null; @Autowired private ConfigurationService configurationService; + @Autowired + private HttpConnectionPoolService httpConnectionPoolService; + /** * Returns the {@link SolrClient} for the Statistics core. * Initializes it if needed. @@ -50,7 +54,9 @@ public class SolrStatisticsCore { log.info("usage-statistics.dbfile: {}", configurationService.getProperty("usage-statistics.dbfile")); try { - solr = new HttpSolrClient.Builder(solrService).build(); + solr = new HttpSolrClient.Builder(solrService) + .withHttpClient(httpConnectionPoolService.getClient()) + .build(); } catch (Exception e) { log.error("Error accessing Solr server configured in 'solr-statistics.server'", e); } diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java index c544ec1659..ee6380ca1d 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.dspace.service.impl.HttpConnectionPoolService; import org.dspace.xoai.services.api.config.ConfigurationService; import org.dspace.xoai.services.api.solr.SolrServerResolver; import org.springframework.beans.factory.annotation.Autowired; @@ -23,12 +24,17 @@ public class DSpaceSolrServerResolver implements SolrServerResolver { @Autowired private ConfigurationService configurationService; + @Autowired + private HttpConnectionPoolService httpConnectionPoolService; + @Override public SolrClient getServer() throws SolrServerException { if (server == null) { String serverUrl = configurationService.getProperty("oai.solr.url"); try { - server = new HttpSolrClient.Builder(serverUrl).build(); + server = new HttpSolrClient.Builder(serverUrl) + .withHttpClient(httpConnectionPoolService.getClient()) + .build(); log.debug("OAI Solr Server Initialized"); } catch (Exception e) { log.error("Could not initialize OAI Solr Server at " + serverUrl , e); diff --git a/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java b/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java index 158f73be1d..25b1a87592 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java @@ -13,6 +13,7 @@ import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.dspace.service.impl.HttpConnectionPoolService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -33,9 +34,15 @@ public class DSpaceSolrServer { if (_server == null) { ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + HttpConnectionPoolService httpConnectionPoolService + = DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName(null, HttpConnectionPoolService.class); String serverUrl = configurationService.getProperty("oai.solr.url"); try { - _server = new HttpSolrClient.Builder(serverUrl).build(); + _server = new HttpSolrClient.Builder(serverUrl) + .withHttpClient(httpConnectionPoolService.getClient()) + .build(); log.debug("OAI Solr Server Initialized"); } catch (Exception e) { log.error("Could not initialize OAI Solr Server at " + serverUrl , e); From bb8d3d853a78ee7a8ac5d9dd890cff4ac82a1de9 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 4 Oct 2021 14:19:02 -0400 Subject: [PATCH 0468/1254] Define pool configuration properties --- dspace/config/dspace.cfg | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a2c27ffad9..27ea8ec2b2 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -45,6 +45,17 @@ default.language = en_US # Since DSpace 7, SOLR must be installed as a stand-alone service solr.server = http://localhost:8983/solr +# Solr connection pool. +# +# Maximum open connections to Solr: +# solr.client.maxTotalConnections = 10 +# +# Maximum open connections per Solr instance: +# solr.client.maxPerRoute = 6 +# +# Default keep-alive time for open Solr connections, in milliseconds: +# solr.client.keepAlive = 5000 + ##### Database settings ##### # DSpace only supports two database types: PostgreSQL or Oracle From 5855850cd158878b8c7ad60d2ea8d6b73eda3ad4 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 4 Oct 2021 16:39:21 -0400 Subject: [PATCH 0469/1254] Make pool service injectable. --- dspace/config/spring/api/core-services.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 413d0b814a..cc4fe29817 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -63,6 +63,9 @@ + + From de9d1a8cd8de86b55f0276825ba0faf51f1f7c07 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 4 Oct 2021 16:40:07 -0400 Subject: [PATCH 0470/1254] Initialize correctly. More generous pool defaults. --- .../impl/HttpConnectionPoolService.java | 31 ++++++++++--------- dspace/config/dspace.cfg | 4 +-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java index 0eeb93a441..010ba1d239 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.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.service.impl; import java.util.concurrent.TimeUnit; @@ -30,15 +37,15 @@ public class HttpConnectionPoolService { @Inject private ConfigurationService configurationService; - private final PoolingHttpClientConnectionManager connManager; + private final PoolingHttpClientConnectionManager connManager + = new PoolingHttpClientConnectionManager(); - private final Thread connectionMonitor; + private final ConnectionKeepAliveStrategy keepAliveStrategy + = new KeepAliveStrategy(); - private final ConnectionKeepAliveStrategy keepAliveStrategy; + private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; - private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 10; - - private static final int DEFAULT_MAX_PER_ROUTE = 6; + private static final int DEFAULT_MAX_PER_ROUTE = 15; private static final int DEFAULT_KEEPALIVE = 5 * 1000; // milliseconds @@ -46,22 +53,16 @@ public class HttpConnectionPoolService { private static final int IDLE_INTERVAL = 30; // seconds - public HttpConnectionPoolService() { - connManager = new PoolingHttpClientConnectionManager(); + @PostConstruct + protected void init() { connManager.setMaxTotal(configurationService.getIntProperty( "solrClient.maxTotalConnections", DEFAULT_MAX_TOTAL_CONNECTIONS)); connManager.setDefaultMaxPerRoute( configurationService.getIntProperty("solrClient.maxPerRoute", DEFAULT_MAX_PER_ROUTE)); - connectionMonitor = new IdleConnectionMonitorThread(connManager); + Thread connectionMonitor = new IdleConnectionMonitorThread(connManager); connectionMonitor.setDaemon(true); - - keepAliveStrategy = new KeepAliveStrategy(); - } - - @PostConstruct - protected void init() { connectionMonitor.start(); } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 27ea8ec2b2..a4bc8618cc 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -48,10 +48,10 @@ solr.server = http://localhost:8983/solr # Solr connection pool. # # Maximum open connections to Solr: -# solr.client.maxTotalConnections = 10 +# solr.client.maxTotalConnections = 20 # # Maximum open connections per Solr instance: -# solr.client.maxPerRoute = 6 +# solr.client.maxPerRoute = 15 # # Default keep-alive time for open Solr connections, in milliseconds: # solr.client.keepAlive = 5000 From 23b0ae1c4d5f168b8ef11feb1e126164a64f2dc8 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 23 Nov 2021 08:57:52 -0500 Subject: [PATCH 0471/1254] Make connection maximum lifetime configurable. #7986 --- .../impl/HttpConnectionPoolService.java | 33 ++++++++++++++++--- dspace/config/dspace.cfg | 3 ++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java index 010ba1d239..c416baffc7 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java @@ -37,24 +37,37 @@ public class HttpConnectionPoolService { @Inject private ConfigurationService configurationService; - private final PoolingHttpClientConnectionManager connManager - = new PoolingHttpClientConnectionManager(); + private PoolingHttpClientConnectionManager connManager; private final ConnectionKeepAliveStrategy keepAliveStrategy = new KeepAliveStrategy(); + /** Maximum number of concurrent pooled connections. */ private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; + /** Maximum number of concurrent pooled connections per route. */ private static final int DEFAULT_MAX_PER_ROUTE = 15; - private static final int DEFAULT_KEEPALIVE = 5 * 1000; // milliseconds + /** Keep connections open at least this long, if the response did not + * specify: milliseconds + */ + private static final int DEFAULT_KEEPALIVE = 5 * 1000; - private static final int CHECK_INTERVAL = 1000; // milliseconds + /** Pooled connection maximum lifetime: seconds */ + private static final int DEFAULT_TTL = 10 * 60; - private static final int IDLE_INTERVAL = 30; // seconds + /** Clean up stale connections this often: milliseconds */ + private static final int CHECK_INTERVAL = 1000; + + /** Connection idle if unused for this long: seconds */ + private static final int IDLE_INTERVAL = 30; @PostConstruct protected void init() { + connManager = new PoolingHttpClientConnectionManager( + configurationService.getIntProperty("solrClient.timeToLive", DEFAULT_TTL), + TimeUnit.SECONDS); + connManager.setMaxTotal(configurationService.getIntProperty( "solrClient.maxTotalConnections", DEFAULT_MAX_TOTAL_CONNECTIONS)); connManager.setDefaultMaxPerRoute( @@ -116,11 +129,17 @@ public class HttpConnectionPoolService { private final HttpClientConnectionManager connMgr; private volatile boolean shutdown; + /** + * Constructor. + * + * @param connMgr the manager to be monitored. + */ public IdleConnectionMonitorThread( PoolingHttpClientConnectionManager connMgr) { super(); this.connMgr = connMgr; } + @Override public void run() { try { @@ -135,6 +154,10 @@ public class HttpConnectionPoolService { shutdown(); } } + + /** + * Cause a controlled exit from the thread. + */ public void shutdown() { shutdown = true; synchronized (this) { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a4bc8618cc..3a3fe71961 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -55,6 +55,9 @@ solr.server = http://localhost:8983/solr # # Default keep-alive time for open Solr connections, in milliseconds: # solr.client.keepAlive = 5000 +# +# Maximum lifetime of a pooled connection, in seconds: +# solr.client.timeToLive = 600 ##### Database settings ##### # DSpace only supports two database types: PostgreSQL or Oracle From c462415a64ffaba28bc7a5e755a8e04a7257bb91 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 23 Nov 2021 14:41:05 -0500 Subject: [PATCH 0472/1254] Add unit test. #7986 --- dspace-api/pom.xml | 75 ++++++++++++++- .../impl/HttpConnectionPoolService.java | 2 +- .../impl/HttpConnectionPoolServiceTest.java | 96 +++++++++++++++++++ 3 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/service/impl/HttpConnectionPoolServiceTest.java diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index abced08999..f3d39e8357 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -903,14 +903,12 @@ org.apache.velocity velocity-engine-core - 2.0 jar org.xmlunit xmlunit-core - 2.6.3 test @@ -934,6 +932,79 @@ test + + org.mock-server + mockserver-junit-rule + 5.11.2 + test + + + + + + org.apache.commons + commons-text + 1.9 + + + io.netty + netty-buffer + 4.1.53.Final + + + io.netty + netty-transport + 4.1.53.Final + + + io.netty + netty-common + 4.1.53.Final + + + io.netty + netty-handler + 4.1.53.Final + + + io.netty + netty-codec + 4.1.53.Final + + + org.apache.velocity + velocity-engine-core + 2.2 + + + org.xmlunit + xmlunit-core + 2.8.0 + test + + + com.github.java-json-tools + json-schema-validator + 2.2.14 + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.3 + + + javax.validation + validation-api + 2.0.1.Final + + + io.swagger + swagger-core + 1.6.2 + + + + diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java index c416baffc7..a1f9aaf16d 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java @@ -35,7 +35,7 @@ import org.dspace.services.ConfigurationService; @Singleton public class HttpConnectionPoolService { @Inject - private ConfigurationService configurationService; + ConfigurationService configurationService; private PoolingHttpClientConnectionManager connManager; diff --git a/dspace-api/src/test/java/org/dspace/service/impl/HttpConnectionPoolServiceTest.java b/dspace-api/src/test/java/org/dspace/service/impl/HttpConnectionPoolServiceTest.java new file mode 100644 index 0000000000..50d20c9a3c --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/service/impl/HttpConnectionPoolServiceTest.java @@ -0,0 +1,96 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.service.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.dspace.AbstractDSpaceTest; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.mockserver.client.MockServerClient; +import org.mockserver.junit.MockServerRule; + +/** + * + * @author Mark H. Wood + */ +public class HttpConnectionPoolServiceTest + extends AbstractDSpaceTest { + private static ConfigurationService configurationService; + + @Rule + public MockServerRule mockServerRule = new MockServerRule(this); + + private MockServerClient mockServerClient; + + @BeforeClass + public static void initClass() { + configurationService = DSpaceServicesFactory.getInstance() + .getConfigurationService(); + } + + /** + * Test of getClient method, of class HttpConnectionPoolService. + * @throws java.io.IOException if a connection cannot be closed. + * @throws java.net.URISyntaxException when an invalid URI is constructed. + */ + @Test + public void testGetClient() + throws IOException, URISyntaxException { + System.out.println("getClient"); + + configurationService.setProperty("solrClient.maxTotalConnections", 2); + configurationService.setProperty("solrClient.maxPerRoute", 2); + HttpConnectionPoolService instance = new HttpConnectionPoolService(); + instance.configurationService = configurationService; + instance.init(); + + final String testPath = "/test"; + mockServerClient.when( + request() + .withPath(testPath) + ).respond( + response() + .withStatusCode(HttpStatus.OK_200) + ); + + try (CloseableHttpClient httpClient = instance.getClient()) { + assertNotNull("getClient should always return a client", httpClient); + + URI uri = new URIBuilder() + .setScheme("http") + .setHost("localhost") + .setPort(mockServerClient.getPort()) + .setPath(testPath) + .build(); + System.out.println(uri.toString()); + HttpUriRequest request = RequestBuilder.get(uri) + .build(); + try (CloseableHttpResponse response = httpClient.execute(request)) { + assertEquals("Response status should be OK", HttpStatus.OK_200, + response.getStatusLine().getStatusCode()); + } + } + } +} From 7fca8582604f6ea73d72eb22f1b0f1198b583e15 Mon Sep 17 00:00:00 2001 From: Clairton Luz Date: Wed, 24 Nov 2021 08:55:42 -0300 Subject: [PATCH 0473/1254] Upgrade aws-java-sdk-s3 version to avoid exception on start "Socket not created by this factory" Current docker imagem `dspace/dspace:dspace-7.1` when enable s3Store into bitstore.xml throw exception when start application beacause of the current version of `aws-java-sdk-s3` is incompatible with java 11. So I upgrade version to resolve the problem. --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index abced08999..872f3f8540 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -855,7 +855,7 @@ com.amazonaws aws-java-sdk-s3 - 1.10.50 + 1.12.116 From 8e62c0496c549960367e49885210f7a5dce81031 Mon Sep 17 00:00:00 2001 From: Joost Date: Thu, 25 Nov 2021 15:33:23 +0100 Subject: [PATCH 0474/1254] [task 82428] setting a timeout on calls to IRUS --- .../export/service/OpenUrlServiceImpl.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java index d2524d7875..ca4434fbc2 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java @@ -14,10 +14,17 @@ import java.net.URLConnection; import java.sql.SQLException; import java.util.Date; import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.tools.ant.taskdefs.condition.Http; import org.dspace.core.Context; import org.dspace.statistics.export.OpenURLTracker; import org.springframework.beans.factory.annotation.Autowired; @@ -63,15 +70,16 @@ public class OpenUrlServiceImpl implements OpenUrlService { * @throws IOException */ protected int getResponseCodeFromUrl(final String urlStr) throws IOException { - URLConnection conn; - URL url = new URL(urlStr); - conn = url.openConnection(); + HttpGet httpGet = new HttpGet(urlStr); + HttpClient httpClient = getHttpClient(); + HttpResponse httpResponse = httpClient.execute(httpGet); + return httpResponse.getStatusLine().getStatusCode(); + } - HttpURLConnection httpURLConnection = (HttpURLConnection) conn; - int responseCode = httpURLConnection.getResponseCode(); - httpURLConnection.disconnect(); - - return responseCode; + HttpClient getHttpClient(){ + //setting a timeout of 10 seconds so the connection pool doesn't exhaust when waiting a long time for a reply. + RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(10*1000).build(); + return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build(); } /** From 800eeff984e0c1838518eff3edf678279ffdb445 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 25 Nov 2021 19:22:47 +0100 Subject: [PATCH 0475/1254] [CST-4882] Return 422 instead of 500 when solr query is not valid --- .../app/rest/DiscoveryRestController.java | 45 ++++++++++---- .../repository/DiscoveryRestRepository.java | 17 ++--- .../app/rest/DiscoveryRestControllerIT.java | 62 ++++++++++++++++++- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java index d167d2a84d..931cf9f126 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java @@ -17,6 +17,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.FacetConfigurationRest; import org.dspace.app.rest.model.FacetResultsRest; @@ -52,6 +53,8 @@ public class DiscoveryRestController implements InitializingBean { private static final Logger log = LogManager.getLogger(); + private static final String SOLR_PARSE_ERROR_MESSAGE = "Cannot parse"; + @Autowired protected Utils utils; @@ -149,13 +152,22 @@ public class DiscoveryRestController implements InitializingBean { } //Get the Search results in JSON format - SearchResultsRest searchResultsRest = discoveryRestRepository - .getSearchObjects(query, dsoTypes, dsoScope, configuration, searchFilters, page, utils.obtainProjection()); + try { + SearchResultsRest searchResultsRest = discoveryRestRepository.getSearchObjects(query, dsoTypes, dsoScope, + configuration, searchFilters, page, utils.obtainProjection()); - //Convert the Search JSON results to paginated HAL resources - SearchResultsResource searchResultsResource = new SearchResultsResource(searchResultsRest, utils, page); - halLinkService.addLinks(searchResultsResource, page); - return searchResultsResource; + //Convert the Search JSON results to paginated HAL resources + SearchResultsResource searchResultsResource = new SearchResultsResource(searchResultsRest, utils, page); + halLinkService.addLinks(searchResultsResource, page); + return searchResultsResource; + } catch (IllegalArgumentException e) { + boolean isParsingException = e.getMessage().contains(SOLR_PARSE_ERROR_MESSAGE); + if (isParsingException) { + throw new UnprocessableEntityException(e.getMessage()); + } else { + throw e; + } + } } @RequestMapping(method = RequestMethod.GET, value = "/facets") @@ -186,6 +198,7 @@ public class DiscoveryRestController implements InitializingBean { configuration, List searchFilters, Pageable page) throws Exception { + System.out.println("FACETS/xxxxx"); dsoTypes = emptyIfNull(dsoTypes); @@ -198,13 +211,23 @@ public class DiscoveryRestController implements InitializingBean { + ", page: " + Objects.toString(page)); } - FacetResultsRest facetResultsRest = discoveryRestRepository - .getFacetObjects(facetName, prefix, query, dsoTypes, dsoScope, configuration, searchFilters, page); + try { + FacetResultsRest facetResultsRest = discoveryRestRepository + .getFacetObjects(facetName, prefix, query, dsoTypes, dsoScope, configuration, searchFilters, page); - FacetResultsResource facetResultsResource = converter.toResource(facetResultsRest); + FacetResultsResource facetResultsResource = converter.toResource(facetResultsRest); - halLinkService.addLinks(facetResultsResource, page); - return facetResultsResource; + halLinkService.addLinks(facetResultsResource, page); + return facetResultsResource; + } catch (Exception e) { + System.out.println("ERROR = " + e.getMessage()); + boolean isParsingException = e.getMessage().contains(SOLR_PARSE_ERROR_MESSAGE); + if (isParsingException) { + throw new UnprocessableEntityException(e.getMessage()); + } else { + throw e; + } + } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java index 682ca834b8..52224ef579 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DiscoveryRestRepository.java @@ -131,7 +131,8 @@ public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { } public FacetResultsRest getFacetObjects(String facetName, String prefix, String query, List dsoTypes, - String dsoScope, final String configuration, List searchFilters, Pageable page) { + String dsoScope, final String configuration, List searchFilters, Pageable page) + throws SearchServiceException { Context context = obtainContext(); @@ -139,17 +140,9 @@ public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { DiscoveryConfiguration discoveryConfiguration = searchConfigurationService .getDiscoveryConfigurationByNameOrDso(configuration, scopeObject); - DiscoverResult searchResult = null; - DiscoverQuery discoverQuery = null; - try { - discoverQuery = queryBuilder.buildFacetQuery(context, scopeObject, discoveryConfiguration, prefix, query, - searchFilters, dsoTypes, page, facetName); - searchResult = searchService.search(context, scopeObject, discoverQuery); - - } catch (SearchServiceException e) { - log.error("Error while searching with Discovery", e); - //TODO TOM handle search exception - } + DiscoverQuery discoverQuery = queryBuilder.buildFacetQuery(context, scopeObject, discoveryConfiguration, prefix, + query, searchFilters, dsoTypes, page, facetName); + DiscoverResult searchResult = searchService.search(context, scopeObject, discoverQuery); FacetResultsRest facetResultsRest = discoverFacetResultsConverter.convert(context, facetName, prefix, query, dsoTypes, dsoScope, searchFilters, searchResult, discoveryConfiguration, page, 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 7e5e6fb7e4..e33159b219 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 @@ -1442,6 +1442,66 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) ; + + getClient().perform(get("/api/discover/search/objects") + .param("query", "test")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/discover/search/objects") + .param("query", "test:")) + .andExpect(status().isUnprocessableEntity()); + + } + + + @Test + public void discoverSearchObjectsTestWithInvalidSolrQuery() 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 with sub-community and two collections. +// parentCommunity = CommunityBuilder.createCommunity(context) +// .withName("Parent Community") +// .build(); +// Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) +// .withName("Sub Community") +// .build(); +// Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); +// Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); +// +// //2. Three public items that are readable by Anonymous with different subjects +// Item publicItem1 = ItemBuilder.createItem(context, col1) +// .withTitle("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") +// .build(); +// +// context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/search/objects") + .param("query", "test")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/discover/search/objects") + .param("query", "test:")) + .andExpect(status().isUnprocessableEntity()); + } @@ -3910,7 +3970,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest getClient().perform(get("/api/discover/search/objects") .param("query", "OR")) - .andExpect(status().isBadRequest()) + .andExpect(status().isUnprocessableEntity()) ; } From 831dc21c94e042b8a261601756f45006d4c36203 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 25 Nov 2021 19:40:38 +0100 Subject: [PATCH 0476/1254] [CST-4882] Code cleanup --- .../app/rest/DiscoveryRestControllerIT.java | 37 ------------------- 1 file changed, 37 deletions(-) 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 e33159b219..aab10de12a 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 @@ -1456,43 +1456,6 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @Test public void discoverSearchObjectsTestWithInvalidSolrQuery() 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 with sub-community and two collections. -// parentCommunity = CommunityBuilder.createCommunity(context) -// .withName("Parent Community") -// .build(); -// Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) -// .withName("Sub Community") -// .build(); -// Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); -// Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); -// -// //2. Three public items that are readable by Anonymous with different subjects -// Item publicItem1 = ItemBuilder.createItem(context, col1) -// .withTitle("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") -// .build(); -// -// context.restoreAuthSystemState(); getClient().perform(get("/api/discover/search/objects") .param("query", "test")) From f1794362843a2152837b2563a2c3c48813662010 Mon Sep 17 00:00:00 2001 From: Ben Bosman Date: Fri, 26 Nov 2021 11:57:06 +0100 Subject: [PATCH 0477/1254] Avoid incorrect sort --- .../main/java/org/dspace/content/DSpaceObjectServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index c34291c3dd..8cf0c92d2c 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -625,7 +625,9 @@ public abstract class DSpaceObjectServiceImpl implements public int compare(MetadataValue o1, MetadataValue o2) { int compare = o1.getPlace() - o2.getPlace(); if (compare == 0) { - if (o1 instanceof RelationshipMetadataValue) { + if (o1 instanceof RelationshipMetadataValue && o2 instanceof RelationshipMetadataValue) { + return compare; + } else if (o1 instanceof RelationshipMetadataValue) { return 1; } else if (o2 instanceof RelationshipMetadataValue) { return -1; From 9df531299b5d0d65383ebc7f3842a437bc73365d Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 4 Nov 2021 12:29:01 +0100 Subject: [PATCH 0478/1254] Add migration to fix MDV place --- ...DV_place_after_migrating_from_DSpace_5.sql | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql new file mode 100644 index 0000000000..eb480dd1bf --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql @@ -0,0 +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/ +-- + +---------------------------------------------------- +-- Make sure the metadatavalue.place column starts at 0 instead of 1 +---------------------------------------------------- +UPDATE metadatavalue AS mdv +SET place = mdv.place - minplace +FROM ( + SELECT dspace_object_id, metadata_field_id, MIN(place) AS minplace + FROM metadatavalue + GROUP BY dspace_object_id, metadata_field_id + ) AS mp +WHERE mdv.dspace_object_id = mp.dspace_object_id + AND mdv.metadata_field_id = mp.metadata_field_id + AND minplace > 0; From 2939ae0a167d9d2495c614e485f1a82000a2a51f Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 25 Nov 2021 12:58:22 +0100 Subject: [PATCH 0479/1254] 85137: Add Oracle migration --- ...DV_place_after_migrating_from_DSpace_5.sql | 24 +++++++++++++++++++ ...DV_place_after_migrating_from_DSpace_5.sql | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql new file mode 100644 index 0000000000..9c39c15e66 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql @@ -0,0 +1,24 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +---------------------------------------------------- +-- Make sure the metadatavalue.place column starts at 0 instead of 1 +---------------------------------------------------- +MERGE INTO metadatavalue mdv +USING ( + SELECT dspace_object_id, metadata_field_id, MIN(place) AS minplace + FROM metadatavalue + GROUP BY dspace_object_id, metadata_field_id +) mp +ON ( + mdv.dspace_object_id = mp.dspace_object_id + AND mdv.metadata_field_id = mp.metadata_field_id + AND mp.minplace > 0 +) +WHEN MATCHED THEN UPDATE +SET mdv.place = mdv.place - mp.minplace; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql index eb480dd1bf..5523a7e7ec 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql @@ -18,4 +18,4 @@ FROM ( ) AS mp WHERE mdv.dspace_object_id = mp.dspace_object_id AND mdv.metadata_field_id = mp.metadata_field_id - AND minplace > 0; + AND minplace > 0; \ No newline at end of file From e37fd1df8f1b58a82c54d97d2bf424fced52851b Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 25 Nov 2021 13:46:55 +0100 Subject: [PATCH 0480/1254] 85137: Add H2 migration --- ...DV_place_after_migrating_from_DSpace_5.sql | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql new file mode 100644 index 0000000000..3b649a321c --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql @@ -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/ +-- + +---------------------------------------------------- +-- Make sure the metadatavalue.place column starts at 0 instead of 1 +---------------------------------------------------- +CREATE LOCAL TEMPORARY TABLE mdv_minplace ( + dspace_object_id UUID NOT NULL, + metadata_field_id INT NOT NULL, + minplace INT NOT NULL, +); + +INSERT INTO mdv_minplace +SELECT dspace_object_id, metadata_field_id, MIN(place) AS minplace +FROM metadatavalue +GROUP BY dspace_object_id, metadata_field_id; + +UPDATE metadatavalue AS mdv +SET place = mdv.place - ( + SELECT minplace FROM mdv_minplace AS mp + WHERE mp.dspace_object_id = mdv.dspace_object_id + AND mp.metadata_field_id = mdv.metadata_field_id +); From c2494db11a8cb19ce235d38a386bb204ef7806b6 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 29 Nov 2021 16:47:30 +0100 Subject: [PATCH 0481/1254] Fixed context's mode handling to improve performances --- dspace-api/src/main/java/org/dspace/core/Context.java | 10 ++++++---- .../resources/spring/spring-dspace-core-services.xml | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 00130f1dc3..8d448e92e8 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -111,7 +111,7 @@ public class Context implements AutoCloseable { /** * Context mode */ - private Mode mode = Mode.READ_WRITE; + private Mode mode; /** * Cache that is only used the context is in READ_ONLY mode @@ -129,7 +129,6 @@ public class Context implements AutoCloseable { } protected Context(EventService eventService, DBConnection dbConnection) { - this.mode = Mode.READ_WRITE; this.eventService = eventService; this.dbConnection = dbConnection; init(); @@ -141,7 +140,6 @@ public class Context implements AutoCloseable { * No user is authenticated. */ public Context() { - this.mode = Mode.READ_WRITE; init(); } @@ -184,7 +182,11 @@ public class Context implements AutoCloseable { authStateChangeHistory = new ConcurrentLinkedDeque<>(); authStateClassCallHistory = new ConcurrentLinkedDeque<>(); - setMode(this.mode); + + if (this.mode != null) { + setMode(this.mode); + } + } /** diff --git a/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml index cdea1c81fe..87bfcbc86c 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-core-services.xml @@ -37,7 +37,7 @@ - + From 33a07d2f14eb98b1eac3130b35b20551b7a509cb Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 29 Nov 2021 18:06:45 +0100 Subject: [PATCH 0482/1254] Fix tests --- dspace-api/src/main/java/org/dspace/core/Context.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 8d448e92e8..43cd3e6fa2 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -775,7 +775,7 @@ public class Context implements AutoCloseable { * @return The current mode */ public Mode getCurrentMode() { - return mode; + return mode != null ? mode : Mode.READ_WRITE; } /** From 651ecbf4589f3868c2daea0ab259693d3ec6d571 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 29 Nov 2021 18:52:03 +0100 Subject: [PATCH 0483/1254] Fixed tests context mode --- .../java/org/dspace/AbstractIntegrationTestWithDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java index 2b6a73673a..402947b966 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java @@ -105,7 +105,7 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati public void setUp() throws Exception { try { //Start a new context - context = new Context(Context.Mode.BATCH_EDIT); + context = new Context(Context.Mode.READ_WRITE); context.turnOffAuthorisationSystem(); //Find our global test EPerson account. If it doesn't exist, create it. From 16e704a285f69ba2406791010db332decde7eb59 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 1 Dec 2021 15:33:13 +0100 Subject: [PATCH 0484/1254] 85276: Store and retrieve the Authentication method in the JWT --- .../authenticate/AuthenticationMethod.java | 8 +++ .../AuthenticationServiceImpl.java | 13 +++++ .../dspace/authenticate/IPAuthentication.java | 5 ++ .../authenticate/LDAPAuthentication.java | 18 +++++++ .../authenticate/PasswordAuthentication.java | 15 ++++++ .../authenticate/ShibAuthentication.java | 9 ++++ .../authenticate/X509Authentication.java | 14 +++++ .../service/AuthenticationService.java | 9 ++++ .../main/java/org/dspace/core/Context.java | 12 +++++ .../rest/AuthenticationRestController.java | 1 + .../rest/model/AuthenticationStatusRest.java | 9 ++++ .../AuthenticationMethodClaimProvider.java | 54 +++++++++++++++++++ .../rest/AuthenticationRestControllerIT.java | 24 ++++++++- .../app/rest/ShibbolethRestControllerIT.java | 13 ++++- 14 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/AuthenticationMethodClaimProvider.java diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java index 12c925b485..25d31776cc 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java @@ -216,4 +216,12 @@ public interface AuthenticationMethod { * @return The authentication method name */ public String getName(); + + /** + * Get whether the authentication method is being used. + * @param context The DSpace context + * @param request The current request + * @return whether the authentication method is being used. + */ + public boolean isUsed(Context context, HttpServletRequest request); } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java index eb5e0a03f9..13aa581e2b 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java @@ -193,4 +193,17 @@ public class AuthenticationServiceImpl implements AuthenticationService { public Iterator authenticationMethodIterator() { return getAuthenticationMethodStack().iterator(); } + + public String getAuthenticationMethod(final Context context, final HttpServletRequest request) { + final Iterator authenticationMethodIterator = authenticationMethodIterator(); + + while (authenticationMethodIterator.hasNext()) { + final AuthenticationMethod authenticationMethod = authenticationMethodIterator.next(); + if (authenticationMethod.isUsed(context, request)) { + return authenticationMethod.getName(); + } + } + + return null; + } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java index 69a3b75a28..67405b5c1c 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/IPAuthentication.java @@ -273,4 +273,9 @@ public class IPAuthentication implements AuthenticationMethod { public String getName() { return "ip"; } + + @Override + public boolean isUsed(final Context context, final HttpServletRequest request) { + return false; + } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index 16f2ec3833..3f9659f0c3 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -83,6 +83,9 @@ public class LDAPAuthentication protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private static final String LDAP_AUTHENTICATED = "ldap.authenticated"; + + /** * Let a real auth method return true if it wants. * @@ -261,6 +264,7 @@ public class LDAPAuthentication if (ldap.ldapAuthenticate(dn, password, context)) { context.setCurrentUser(eperson); + request.getSession().setAttribute(LDAP_AUTHENTICATED, true); // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); @@ -311,6 +315,8 @@ public class LDAPAuthentication context.dispatchEvents(); context.restoreAuthSystemState(); context.setCurrentUser(eperson); + request.getSession().setAttribute(LDAP_AUTHENTICATED, true); + // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); @@ -341,6 +347,8 @@ public class LDAPAuthentication ePersonService.update(context, eperson); context.dispatchEvents(); context.setCurrentUser(eperson); + request.getSession().setAttribute(LDAP_AUTHENTICATED, true); + // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); @@ -734,4 +742,14 @@ public class LDAPAuthentication } } } + + @Override + public boolean isUsed(final Context context, final HttpServletRequest request) { + if (request != null && + context.getCurrentUser() != null && + request.getSession().getAttribute(LDAP_AUTHENTICATED) != null) { + return true; + } + return false; + } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java index d14d0595b2..f3f79151ff 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java @@ -51,6 +51,9 @@ public class PasswordAuthentication */ private static final Logger log = LogManager.getLogger(); + private static final String PASSWORD_AUTHENTICATED = "password.authenticated"; + + /** * Look to see if this email address is allowed to register. @@ -216,6 +219,7 @@ public class PasswordAuthentication .checkPassword(context, eperson, password)) { // login is ok if password matches: context.setCurrentUser(eperson); + request.getSession().setAttribute(PASSWORD_AUTHENTICATED, true); log.info(LogHelper.getHeader(context, "authenticate", "type=PasswordAuthentication")); return SUCCESS; } else { @@ -247,4 +251,15 @@ public class PasswordAuthentication public String getName() { return "password"; } + + + @Override + public boolean isUsed(final Context context, final HttpServletRequest request) { + if (request != null && + context.getCurrentUser() != null && + request.getSession().getAttribute(PASSWORD_AUTHENTICATED) != null) { + return true; + } + return false; + } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java index 53502a22ce..868ae7a9c8 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java @@ -1283,5 +1283,14 @@ public class ShibAuthentication implements AuthenticationMethod { } + @Override + public boolean isUsed(final Context context, final HttpServletRequest request) { + if (request != null && + context.getCurrentUser() != null && + request.getSession().getAttribute("shib.authenticated") != null) { + return true; + } + return false; + } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java index 051be09cbf..39e246f3cc 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java @@ -128,6 +128,8 @@ public class X509Authentication implements AuthenticationMethod { protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private static final String X509_AUTHENTICATED = "x509.authenticated"; + /** * Initialization: Set caPublicKey and/or keystore. This loads the @@ -544,6 +546,7 @@ public class X509Authentication implements AuthenticationMethod { context.dispatchEvents(); context.restoreAuthSystemState(); context.setCurrentUser(eperson); + request.getSession().setAttribute(X509_AUTHENTICATED, true); setSpecialGroupsFlag(request, email); return SUCCESS; } else { @@ -563,6 +566,7 @@ public class X509Authentication implements AuthenticationMethod { log.info(LogHelper.getHeader(context, "login", "type=x509certificate")); context.setCurrentUser(eperson); + request.getSession().setAttribute(X509_AUTHENTICATED, true); setSpecialGroupsFlag(request, email); return SUCCESS; } @@ -594,4 +598,14 @@ public class X509Authentication implements AuthenticationMethod { public String getName() { return "x509"; } + + @Override + public boolean isUsed(final Context context, final HttpServletRequest request) { + if (request != null && + context.getCurrentUser() != null && + request.getSession().getAttribute(X509_AUTHENTICATED) != null) { + return true; + } + return false; + } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java b/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java index d4a1cd5d0d..fba2f00323 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java @@ -168,4 +168,13 @@ public interface AuthenticationService { */ public Iterator authenticationMethodIterator(); + /** + * Retrieves the currently used authentication method name based on the context and the request + * + * @param context A valid DSpace context. + * @param request The request that started this operation, or null if not applicable. + * @return the currently used authentication method name + */ + public String getAuthenticationMethod(Context context, HttpServletRequest request); + } diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 00130f1dc3..79f5070a72 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -98,6 +98,11 @@ public class Context implements AutoCloseable { */ private List specialGroupsPreviousState; + /** + * The currently used authentication method + */ + private String authenticationMethod; + /** * Content events */ @@ -890,4 +895,11 @@ public class Context implements AutoCloseable { currentUser = reloadEntity(currentUser); } + public String getAuthenticationMethod() { + return authenticationMethod; + } + + public void setAuthenticationMethod(final String authenticationMethod) { + this.authenticationMethod = authenticationMethod; + } } 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 87f476e02d..9660d0af56 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 @@ -118,6 +118,7 @@ public class AuthenticationRestController implements InitializingBean { response.setHeader("WWW-Authenticate", authenticateHeaderValue); } + authenticationStatusRest.setAuthenticationMethod(context.getAuthenticationMethod()); authenticationStatusRest.setProjection(projection); AuthenticationStatusResource authenticationStatusResource = converter.toResource(authenticationStatusRest); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java index a137620e6b..81a59bbd69 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java @@ -16,6 +16,7 @@ import org.dspace.app.rest.RestResourceController; public class AuthenticationStatusRest extends BaseObjectRest { private boolean okay; private boolean authenticated; + private String authenticationMethod; public static final String NAME = "status"; public static final String CATEGORY = RestAddressableModel.AUTHENTICATION; @@ -81,4 +82,12 @@ public class AuthenticationStatusRest extends BaseObjectRest { public void setOkay(boolean okay) { this.okay = okay; } + + public String getAuthenticationMethod() { + return authenticationMethod; + } + + public void setAuthenticationMethod(final String authenticationMethod) { + this.authenticationMethod = authenticationMethod; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/AuthenticationMethodClaimProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/AuthenticationMethodClaimProvider.java new file mode 100644 index 0000000000..84b8259dce --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/AuthenticationMethodClaimProvider.java @@ -0,0 +1,54 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security.jwt; + +import java.sql.SQLException; +import java.text.ParseException; +import javax.servlet.http.HttpServletRequest; + +import com.nimbusds.jwt.JWTClaimsSet; +import org.dspace.authenticate.service.AuthenticationService; +import org.dspace.core.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Provides a claim for a JSON Web Token, this claim is responsible for adding the authentication method to it + */ +@Component +public class AuthenticationMethodClaimProvider implements JWTClaimProvider { + + public static final String AUTHENTICATION_METHOD = "authenticationMethod"; + + private static final Logger log = LoggerFactory.getLogger(AuthenticationMethodClaimProvider.class); + + @Autowired + private AuthenticationService authenticationService; + + public String getKey() { + return AUTHENTICATION_METHOD; + } + + public Object getValue(final Context context, final HttpServletRequest request) { + if (context.getAuthenticationMethod() != null) { + return context.getAuthenticationMethod(); + } + return authenticationService.getAuthenticationMethod(context, request); + } + + public void parseClaim(final Context context, final HttpServletRequest request, final JWTClaimsSet jwtClaimsSet) + throws SQLException { + try { + context.setAuthenticationMethod(jwtClaimsSet.getStringClaim(AUTHENTICATION_METHOD)); + } catch (ParseException e) { + log.error(e.getMessage(), e); + } + } +} 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 5584fbface..bb75a95924 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 @@ -107,6 +107,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("password"))) .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) @@ -136,6 +137,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("password"))) .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) @@ -159,6 +161,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(false))) + .andExpect(jsonPath("$.authenticationMethod").doesNotExist()) .andExpect(jsonPath("$.type", is("status"))) .andExpect(header().string("WWW-Authenticate", "password realm=\"DSpace REST API\"")); @@ -213,6 +216,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) .andExpect(jsonPath("$.type", is("status"))) // Verify that the CSRF token has NOT been changed... status checks won't change the token // (only login/logout will) @@ -244,6 +248,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) .andExpect(jsonPath("$.type", is("status"))); //Logout, invalidating the token @@ -260,12 +265,18 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // Verify /api/authn/shibboleth endpoint does not work // NOTE: this is the same call as in testStatusShibAuthenticatedWithCookie()) - getClient().perform(get("/api/authn/shibboleth") + String token = getClient().perform(get("/api/authn/shibboleth") .header("Referer", "https://myshib.example.com") .param("redirectUrl", uiURL) .requestAttr("SHIB-MAIL", eperson.getEmail()) .requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff")) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()) + .andReturn().getResponse().getHeader("Authorization"); + + getClient(token).perform(get("/api/authn/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authenticated", is(false))) + .andExpect(jsonPath("$.authenticationMethod").doesNotExist()); } // NOTE: This test is similar to testStatusShibAuthenticatedWithCookie(), but proves the same process works @@ -453,6 +464,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(false))) + .andExpect(jsonPath("$.authenticationMethod").doesNotExist()) .andExpect(jsonPath("$.type", is("status"))); } @@ -515,6 +527,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("password"))) .andExpect(jsonPath("$.type", is("status"))); // Logout, invalidating token @@ -858,6 +871,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) .andExpect(jsonPath("$._embedded.eperson", @@ -901,6 +915,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) .andExpect(jsonPath("$._embedded.eperson", @@ -921,6 +936,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) .andExpect(jsonPath("$.type", is("status"))) .andExpect(jsonPath("$._links.eperson.href", startsWith(REST_SERVER_URL))) .andExpect(jsonPath("$._embedded.eperson", @@ -954,6 +970,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("password"))) .andExpect(jsonPath("$.type", is("status"))); //Logout @@ -965,6 +982,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(false))) + .andExpect(jsonPath("$.authenticationMethod").doesNotExist()) .andExpect(jsonPath("$.type", is("status"))); //Simulate that a shibboleth authentication has happened @@ -979,6 +997,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) .andExpect(jsonPath("$.type", is("status"))); //Logout @@ -990,6 +1009,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.okay", is(true))) .andExpect(jsonPath("$.authenticated", is(false))) + .andExpect(jsonPath("$.authenticationMethod").doesNotExist()) .andExpect(jsonPath("$.type", is("status"))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java index 4d3e99c449..c9fe711e0b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java @@ -7,7 +7,9 @@ */ package org.dspace.app.rest; +import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -47,9 +49,16 @@ public class ShibbolethRestControllerIT extends AbstractControllerIntegrationTes // unauthenticated, but it must include some expected SHIB attributes. // SHIB-MAIL attribute is the default email header sent from Shibboleth after a successful login. // In this test we are simply mocking that behavior by setting it to an existing EPerson. - getClient().perform(get("/api/authn/shibboleth").requestAttr("SHIB-MAIL", eperson.getEmail())) + String token = getClient().perform(get("/api/authn/shibboleth").requestAttr("SHIB-MAIL", eperson.getEmail())) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost:4000")); + .andExpect(redirectedUrl("http://localhost:4000")) + .andReturn().getResponse().getHeader("Authorization"); + + + getClient(token).perform(get("/api/authn/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); } @Test From 6b5ae6f52827b6ed0c0ca3e66a56a662c824a777 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 3 Nov 2021 16:28:48 -0700 Subject: [PATCH 0485/1254] Updates to range processing including view hint. --- .../iiif/model/generator/RangeGenerator.java | 10 ++ .../app/iiif/service/ManifestService.java | 75 +++++------- .../dspace/app/iiif/service/RangeService.java | 107 +++++++++++++++++- 3 files changed, 141 insertions(+), 51 deletions(-) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java index 726f789535..fe8acf31c4 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/model/generator/RangeGenerator.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.List; import javax.validation.constraints.NotNull; +import de.digitalcollections.iiif.model.enums.ViewingHint; import de.digitalcollections.iiif.model.sharedcanvas.Canvas; import de.digitalcollections.iiif.model.sharedcanvas.Range; import de.digitalcollections.iiif.model.sharedcanvas.Resource; @@ -32,6 +33,7 @@ public class RangeGenerator implements IIIFResource { private String identifier; private String label; + private final List viewingHint = new ArrayList<>(); private final List canvasList = new ArrayList<>(); private final List rangesList = new ArrayList<>(); private final RangeService rangeService; @@ -69,6 +71,11 @@ public class RangeGenerator implements IIIFResource { return this; } + public RangeGenerator addViewingHint(String hint) { + viewingHint.add(new BehaviorGenerator().setType(hint).generateValue()); + return this; + } + /** * Adds canvas to range canvas list. * @param canvas list of canvas generators @@ -99,6 +106,9 @@ public class RangeGenerator implements IIIFResource { } else { range = new Range(identifier); } + if (viewingHint.size() > 0) { + range.setViewingHints(viewingHint); + } for (Canvas canvas : canvasList) { range.addCanvas(canvas); } 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 794fbb46ca..f4614a3db3 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 @@ -9,7 +9,6 @@ package org.dspace.app.iiif.service; import java.sql.SQLException; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -29,7 +28,6 @@ import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.core.I18nUtil; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -125,7 +123,7 @@ public class ManifestService extends AbstractResourceService { addMetadata(context, item); addViewingHint(item); addThumbnail(item, context); - addRanges(context, item, manifestId); + addCanvasAndRange(context, item, manifestId); manifestGenerator.addSequence( sequenceService.getSequence(item)); addRendering(item, context); @@ -133,68 +131,49 @@ public class ManifestService extends AbstractResourceService { } /** - * Add the ranges to the manifest structure. Ranges are generated from the + * Adds canvases and ranges to the manifest. Ranges are generated from the * iiif.toc metadata * * @param context the DSpace Context * @param item the DSpace Item to represent * @param manifestId the generated manifestId */ - private void addRanges(Context context, Item item, String manifestId) { + private void addCanvasAndRange(Context context, Item item, String manifestId) { + // Process the bitstreams in each bundle. List bundles = utils.getIIIFBundles(item); - RangeGenerator root = new RangeGenerator(rangeService); - root.setLabel(I18nUtil.getMessage("iiif.toc.root-label")); - root.setIdentifier(manifestId + "/range/r0"); - - Map tocRanges = new LinkedHashMap(); for (Bundle bnd : bundles) { String bundleToCPrefix = null; if (bundles.size() > 1) { bundleToCPrefix = utils.getBundleIIIFToC(bnd); } - RangeGenerator lastRange = root; - for (Bitstream b : utils.getIIIFBitstreams(context, bnd)) { - CanvasGenerator canvasId = sequenceService.addCanvas(context, item, bnd, b); - List tocs = utils.getIIIFToCs(b, bundleToCPrefix); + // Set the root range for this manifest. + rangeService.setInitialRange(manifestId); + for (Bitstream bitstream : utils.getIIIFBitstreams(context, bnd)) { + + // Add the canvas to the manifest's sequence. + CanvasGenerator canvasGenerator = sequenceService.addCanvas(context, item, bnd, bitstream); + + // Now process ranges. + List tocs = utils.getIIIFToCs(bitstream, bundleToCPrefix); if (tocs.size() > 0) { - for (String toc : tocs) { - RangeGenerator currRange = root; - String[] parts = toc.split(IIIFUtils.TOC_SEPARATOR_REGEX); - String key = ""; - for (int pIdx = 0; pIdx < parts.length; pIdx++) { - if (pIdx > 0) { - key += IIIFUtils.TOC_SEPARATOR; - } - key += parts[pIdx]; - if (tocRanges.get(key) != null) { - currRange = tocRanges.get(key); - } else { - // create the sub range - RangeGenerator range = new RangeGenerator(rangeService); - range.setLabel(parts[pIdx]); - // add the range reference to the currRange so to get an identifier - currRange.addSubRange(range); - - // add the range to the manifest -// manifestGenerator.addRange(range); - - // move the current range - currRange = range; - tocRanges.put(key, range); - } - } - // add the bitstream canvas to the currRange - currRange - .addCanvas(canvasService.getRangeCanvasReference(canvasId.getIdentifier())); - lastRange = currRange; - } + // If toc fields exist for the bitstream, start a new range. + rangeService.setTocRange(tocs, canvasGenerator); } else { - lastRange.addCanvas(canvasService.getRangeCanvasReference(canvasId.getIdentifier())); + // If ranges have been created, add canvas ids to the current range. + if (rangeService.getTocRanges().size() > 0) { + RangeGenerator currentRange = rangeService.getLastRange(); + currentRange.addCanvas(canvasService.getRangeCanvasReference( + canvasGenerator.getIdentifier()) + ); + } } } } - if (tocRanges.size() > 0) { - manifestGenerator.addRange(root); + // Finally, if ranges were found add them to manifest's structures element. + Map tocRanges = rangeService.getTocRanges(); + if (tocRanges != null && tocRanges.size() > 0) { + RangeGenerator rootRange = rangeService.getRootRange(); + manifestGenerator.addRange(rootRange); for (RangeGenerator range : tocRanges.values()) { manifestGenerator.addRange(range); } diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java index 2021572e5c..1a9de54115 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java @@ -7,7 +7,14 @@ */ package org.dspace.app.iiif.service; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.dspace.app.iiif.model.generator.CanvasGenerator; import org.dspace.app.iiif.model.generator.RangeGenerator; +import org.dspace.app.iiif.service.utils.IIIFUtils; +import org.dspace.core.I18nUtil; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -28,14 +35,108 @@ public class RangeService extends AbstractResourceService { @Autowired CanvasService canvasService; + private Map tocRanges = new LinkedHashMap(); + private RangeGenerator lastRange; + private RangeGenerator root; + + public RangeService(ConfigurationService configurationService) { setConfiguration(configurationService); } /** - * Ranges expect the Sub range object to have only an identifier. - * - * @param range the sub range to reference + * Returns the current range to which canvases should be added. The "last" + * range is updated every time a new toc entry is processed. + * @return + */ + public RangeGenerator getLastRange() { + return this.lastRange; + } + + /** + * Get the root range generator. This will contain table of contents entries. + * @return + */ + public RangeGenerator getRootRange() { + return root; + } + + /** + * Sets the root range generator to which sub-ranges will be added. + * @param manifestId id of the manifest to which ranges will be added. + */ + public void setInitialRange(String manifestId) { + root = getRootGenerator(manifestId); + } + + + private RangeGenerator getRootGenerator(String manifestId) { + // The root range that will be used for this manifest. + RangeGenerator root = new RangeGenerator(this); + // This hint is required. + root.addViewingHint("top"); + root.setLabel(I18nUtil.getMessage("iiif.toc.root-label")); + root.setIdentifier(manifestId + "/range/r0"); + return root; + } + + /** + * Gets the current ranges. + * @return map of toc ranges. + */ + public Map getTocRanges() { + return this.tocRanges; + } + + /** + * When the bitstream has a toc metadata field, this creates a new range and adds the first canvas. + * If the toc metadata includes a separator, sub-ranges are created. + * @param tocs ranges from toc metadata + * @param canvasGenerator generator for the current canvas + * @return + */ + public void setTocRange(List tocs , CanvasGenerator canvasGenerator) { + + for (String toc : tocs) { + // Set the current range. The first time called, this will be the root range. + RangeGenerator currRange = root; + String[] parts = toc.split(IIIFUtils.TOC_SEPARATOR_REGEX); + String key = ""; + // Process range and sub-ranges. + for (int pIdx = 0; pIdx < parts.length; pIdx++) { + if (pIdx > 0) { + key += IIIFUtils.TOC_SEPARATOR; + } + key += parts[pIdx]; + if (tocRanges.get(key) != null) { + // This handles the case of a bitstream that crosses two ranges. + currRange = tocRanges.get(key); + } else { + // Create the new range + RangeGenerator range = new RangeGenerator(this); + range.setLabel(parts[pIdx]); + // Add it to the root range + currRange.addSubRange(range); + + // Current range is now the new sub-range + currRange = range; + // Add sub-range to the list. + tocRanges.put(key, range); + } + } + // Add a canvas to the current range. + currRange + .addCanvas(canvasService.getRangeCanvasReference(canvasGenerator.getIdentifier())); + + // Finally, update the range that will be used in the next iteration. + lastRange = currRange; + } +} + + /** + * Ranges expect the sub-range object to have only an identifier. + * + * @param range the sub-range to reference * @return RangeGenerator able to create the reference */ public RangeGenerator getRangeReference(RangeGenerator range) { From 7ba9894d8ecf6a781f811f278f9abcca7b2d17cd Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 3 Nov 2021 17:22:06 -0700 Subject: [PATCH 0486/1254] Fixed bundle ranges. --- .../app/iiif/service/ManifestService.java | 24 ++++++++++--------- .../dspace/app/iiif/service/RangeService.java | 8 +++++-- 2 files changed, 19 insertions(+), 13 deletions(-) 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 f4614a3db3..f19a6a5b95 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 @@ -139,6 +139,10 @@ public class ManifestService extends AbstractResourceService { * @param manifestId the generated manifestId */ private void addCanvasAndRange(Context context, Item item, String manifestId) { + + // Set the root range for this manifest. + rangeService.setInitialRange(manifestId); + // Process the bitstreams in each bundle. List bundles = utils.getIIIFBundles(item); for (Bundle bnd : bundles) { @@ -146,30 +150,28 @@ public class ManifestService extends AbstractResourceService { if (bundles.size() > 1) { bundleToCPrefix = utils.getBundleIIIFToC(bnd); } - // Set the root range for this manifest. - rangeService.setInitialRange(manifestId); for (Bitstream bitstream : utils.getIIIFBitstreams(context, bnd)) { // Add the canvas to the manifest's sequence. - CanvasGenerator canvasGenerator = sequenceService.addCanvas(context, item, bnd, bitstream); + CanvasGenerator fullCanvas = sequenceService.addCanvas(context, item, bnd, bitstream); - // Now process ranges. + // Now process the ranges. List tocs = utils.getIIIFToCs(bitstream, bundleToCPrefix); if (tocs.size() > 0) { - // If toc fields exist for the bitstream, start a new range. - rangeService.setTocRange(tocs, canvasGenerator); + // If toc fields exist for the bitstream, add a new range. + rangeService.setTocRange(tocs, fullCanvas); } else { - // If ranges have been created, add canvas ids to the current range. + // Add canvas ids to the currently active range. if (rangeService.getTocRanges().size() > 0) { RangeGenerator currentRange = rangeService.getLastRange(); - currentRange.addCanvas(canvasService.getRangeCanvasReference( - canvasGenerator.getIdentifier()) - ); + String canvasIdentifier = fullCanvas.getIdentifier(); + CanvasGenerator simpleCanvas = canvasService.getRangeCanvasReference(canvasIdentifier); + currentRange.addCanvas(simpleCanvas); } } } } - // Finally, if ranges were found add them to manifest's structures element. + // If ranges were created add them to manifest's structures element. Map tocRanges = rangeService.getTocRanges(); if (tocRanges != null && tocRanges.size() > 0) { RangeGenerator rootRange = rangeService.getRootRange(); diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java index 1a9de54115..048f28342f 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java @@ -70,8 +70,12 @@ public class RangeService extends AbstractResourceService { } + /** + * Creates generator for the root table of contents range. + * @param manifestId manifest id + * @return root range generator + */ private RangeGenerator getRootGenerator(String manifestId) { - // The root range that will be used for this manifest. RangeGenerator root = new RangeGenerator(this); // This hint is required. root.addViewingHint("top"); @@ -131,7 +135,7 @@ public class RangeService extends AbstractResourceService { // Finally, update the range that will be used in the next iteration. lastRange = currRange; } -} + } /** * Ranges expect the sub-range object to have only an identifier. From 543317f54cda67c4d72f0fcc3d51c8df654fc258 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 1 Dec 2021 12:14:25 -0800 Subject: [PATCH 0487/1254] More refactoring. --- .../app/iiif/service/ManifestService.java | 36 +++----- .../dspace/app/iiif/service/RangeService.java | 87 +++++++++---------- 2 files changed, 54 insertions(+), 69 deletions(-) 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 f19a6a5b95..b08730173e 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 @@ -131,8 +131,8 @@ public class ManifestService extends AbstractResourceService { } /** - * Adds canvases and ranges to the manifest. Ranges are generated from the - * iiif.toc metadata + * Adds Canvases and Ranges to the manifest. Ranges are generated from bitstream + * or bundle iiif metadata. * * @param context the DSpace Context * @param item the DSpace Item to represent @@ -140,38 +140,24 @@ public class ManifestService extends AbstractResourceService { */ private void addCanvasAndRange(Context context, Item item, String manifestId) { - // Set the root range for this manifest. - rangeService.setInitialRange(manifestId); - - // Process the bitstreams in each bundle. + // Set the root Range for this manifest. + rangeService.setRootRange(manifestId); + // Get bundles that can contain IIIF manifest data. List bundles = utils.getIIIFBundles(item); for (Bundle bnd : bundles) { String bundleToCPrefix = null; if (bundles.size() > 1) { + // Check for bundle Range metadata if multiple IIIF bundles exist. bundleToCPrefix = utils.getBundleIIIFToC(bnd); } for (Bitstream bitstream : utils.getIIIFBitstreams(context, bnd)) { - - // Add the canvas to the manifest's sequence. - CanvasGenerator fullCanvas = sequenceService.addCanvas(context, item, bnd, bitstream); - - // Now process the ranges. - List tocs = utils.getIIIFToCs(bitstream, bundleToCPrefix); - if (tocs.size() > 0) { - // If toc fields exist for the bitstream, add a new range. - rangeService.setTocRange(tocs, fullCanvas); - } else { - // Add canvas ids to the currently active range. - if (rangeService.getTocRanges().size() > 0) { - RangeGenerator currentRange = rangeService.getLastRange(); - String canvasIdentifier = fullCanvas.getIdentifier(); - CanvasGenerator simpleCanvas = canvasService.getRangeCanvasReference(canvasIdentifier); - currentRange.addCanvas(simpleCanvas); - } - } + // Add the Canvas to the manifest Sequence. + CanvasGenerator canvas = sequenceService.addCanvas(context, item, bnd, bitstream); + // Update the Ranges. + rangeService.updateRanges(bitstream, bundleToCPrefix, canvas); } } - // If ranges were created add them to manifest's structures element. + // If Ranges were created, add them to manifest Structures element. Map tocRanges = rangeService.getTocRanges(); if (tocRanges != null && tocRanges.size() > 0) { RangeGenerator rootRange = rangeService.getRootRange(); diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java index 048f28342f..0f0c82a952 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java @@ -14,6 +14,7 @@ import java.util.Map; import org.dspace.app.iiif.model.generator.CanvasGenerator; import org.dspace.app.iiif.model.generator.RangeGenerator; import org.dspace.app.iiif.service.utils.IIIFUtils; +import org.dspace.content.Bitstream; import org.dspace.core.I18nUtil; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -36,7 +37,7 @@ public class RangeService extends AbstractResourceService { CanvasService canvasService; private Map tocRanges = new LinkedHashMap(); - private RangeGenerator lastRange; + private RangeGenerator currentRange; private RangeGenerator root; @@ -44,15 +45,6 @@ public class RangeService extends AbstractResourceService { setConfiguration(configurationService); } - /** - * Returns the current range to which canvases should be added. The "last" - * range is updated every time a new toc entry is processed. - * @return - */ - public RangeGenerator getLastRange() { - return this.lastRange; - } - /** * Get the root range generator. This will contain table of contents entries. * @return @@ -65,23 +57,11 @@ public class RangeService extends AbstractResourceService { * Sets the root range generator to which sub-ranges will be added. * @param manifestId id of the manifest to which ranges will be added. */ - public void setInitialRange(String manifestId) { - root = getRootGenerator(manifestId); - } - - - /** - * Creates generator for the root table of contents range. - * @param manifestId manifest id - * @return root range generator - */ - private RangeGenerator getRootGenerator(String manifestId) { - RangeGenerator root = new RangeGenerator(this); - // This hint is required. + public void setRootRange(String manifestId) { + root = new RangeGenerator(this); root.addViewingHint("top"); root.setLabel(I18nUtil.getMessage("iiif.toc.root-label")); root.setIdentifier(manifestId + "/range/r0"); - return root; } /** @@ -93,52 +73,71 @@ public class RangeService extends AbstractResourceService { } /** - * When the bitstream has a toc metadata field, this creates a new range and adds the first canvas. - * If the toc metadata includes a separator, sub-ranges are created. + * Updates the current range and adds sub-ranges. + * @param bitstream bitstream DSO + * @param bundleToCPrefix range prefix from bundle metadata + * @param canvas the current canvas generator + */ + public void updateRanges(Bitstream bitstream, String bundleToCPrefix, CanvasGenerator canvas) { + List tocs = utils.getIIIFToCs(bitstream, bundleToCPrefix); + if (tocs.size() > 0) { + // Add a new Range. + addTocRange(tocs, canvas); + } else { + // Add canvases to the current Range. + if (tocRanges.size() > 0) { + String canvasIdentifier = canvas.getIdentifier(); + CanvasGenerator simpleCanvas = canvasService.getRangeCanvasReference(canvasIdentifier); + currentRange.addCanvas(simpleCanvas); + } + } + } + + /** + * Adds sub-ranges to the root Range. If the toc metadata includes a separator, + * hierarchical sub-ranges are created. * @param tocs ranges from toc metadata * @param canvasGenerator generator for the current canvas * @return */ - public void setTocRange(List tocs , CanvasGenerator canvasGenerator) { + private void addTocRange(List tocs , CanvasGenerator canvasGenerator) { for (String toc : tocs) { - // Set the current range. The first time called, this will be the root range. - RangeGenerator currRange = root; + // Make tempRange a reference to root. + RangeGenerator tempRange = root; String[] parts = toc.split(IIIFUtils.TOC_SEPARATOR_REGEX); String key = ""; - // Process range and sub-ranges. + // Process sub-ranges. for (int pIdx = 0; pIdx < parts.length; pIdx++) { if (pIdx > 0) { key += IIIFUtils.TOC_SEPARATOR; } key += parts[pIdx]; if (tocRanges.get(key) != null) { - // This handles the case of a bitstream that crosses two ranges. - currRange = tocRanges.get(key); + // Handles the case of a bitstream that crosses two ranges. + tempRange = tocRanges.get(key); } else { - // Create the new range RangeGenerator range = new RangeGenerator(this); range.setLabel(parts[pIdx]); - // Add it to the root range - currRange.addSubRange(range); - - // Current range is now the new sub-range - currRange = range; - // Add sub-range to the list. + // Add sub-range to the root Range + tempRange.addSubRange(range); + // Make tempRange a reference to the new sub-range. + tempRange = range; + // Add new sub-range to the map. tocRanges.put(key, range); } } - // Add a canvas to the current range. - currRange + // Add a simple canvas reference to the sub-range. + tempRange .addCanvas(canvasService.getRangeCanvasReference(canvasGenerator.getIdentifier())); - // Finally, update the range that will be used in the next iteration. - lastRange = currRange; + // Update the current Range. + currentRange = tempRange; } } /** - * Ranges expect the sub-range object to have only an identifier. + * Ranges expect the sub-range to have only an identifier. * * @param range the sub-range to reference * @return RangeGenerator able to create the reference From 17adfa0a804b40389c5595c575c5d3d67c108c59 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 1 Dec 2021 13:00:26 -0800 Subject: [PATCH 0488/1254] Minor updates. --- .../java/org/dspace/app/iiif/service/ManifestService.java | 4 ++-- .../main/java/org/dspace/app/iiif/service/RangeService.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) 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 b08730173e..663258db8b 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 @@ -291,8 +291,8 @@ public class ManifestService extends AbstractResourceService { } /** - * This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds - * it to the Sequence if found. + * This method looks for a PDF in the Item's ORIGINAL bundle and adds + * it as the Rendering resource if found. * * @param item DSpace Item * @param context DSpace context diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java index 0f0c82a952..a1e85f104e 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/RangeService.java @@ -121,13 +121,13 @@ public class RangeService extends AbstractResourceService { range.setLabel(parts[pIdx]); // Add sub-range to the root Range tempRange.addSubRange(range); - // Make tempRange a reference to the new sub-range. - tempRange = range; // Add new sub-range to the map. tocRanges.put(key, range); + // Make tempRange a reference to the new sub-range. + tempRange = range; } } - // Add a simple canvas reference to the sub-range. + // Add a simple canvas reference to the Range. tempRange .addCanvas(canvasService.getRangeCanvasReference(canvasGenerator.getIdentifier())); From 83cf4299bf78b380473dc27b0f312891cd0e12a2 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 1 Dec 2021 13:00:46 -0800 Subject: [PATCH 0489/1254] Updated ITs to check viewing hint. --- .../test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java | 3 +++ 1 file changed, 3 insertions(+) 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 00ed1ba11e..f8d1aabaa8 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 @@ -407,6 +407,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.structures[0].@id", Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0"))) .andExpect(jsonPath("$.structures[0].label", is("Table of Contents"))) + .andExpect(jsonPath("$.structures[0].viewingHint", is("top"))) .andExpect(jsonPath("$.structures[0].ranges[0]", Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) .andExpect(jsonPath("$.structures[0].ranges[1]", @@ -484,6 +485,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.structures[0].@id", Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0"))) .andExpect(jsonPath("$.structures[0].label", is("Table of Contents"))) + .andExpect(jsonPath("$.structures[0].viewingHint", is("top"))) .andExpect(jsonPath("$.structures[0].ranges[0]", Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) .andExpect(jsonPath("$.structures[0].ranges[1]", @@ -636,6 +638,7 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0"))) // the toc contains two top sections 1 & 2 without direct children canvases .andExpect(jsonPath("$.structures[0].label", is("Table of Contents"))) + .andExpect(jsonPath("$.structures[0].viewingHint", is("top"))) .andExpect(jsonPath("$.structures[0].ranges", Matchers.hasSize(2))) .andExpect(jsonPath("$.structures[0].ranges[0]", Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) From e02b13ab19f9823a638f6321d9df0d530f486528 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 25 Oct 2021 10:53:53 -0700 Subject: [PATCH 0490/1254] Updated processOptions for iiif. Updated processContentsFile. Style check fix. --- .../app/itemimport/ItemImportServiceImpl.java | 224 ++++++++++++++++-- 1 file changed, 208 insertions(+), 16 deletions(-) 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 d860b0e25a..106e0e532a 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 @@ -1129,7 +1129,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea if (iAssetstore == -1 || sFilePath == null) { System.out.println("\tERROR: invalid contents file line"); System.out.println("\t\tSkipping line: " - + sRegistrationLine); + + sRegistrationLine); continue; } @@ -1153,9 +1153,9 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea registerBitstream(c, i, iAssetstore, sFilePath, sBundle, sDescription); System.out.println("\tRegistering Bitstream: " + sFilePath - + "\tAssetstore: " + iAssetstore - + "\tBundle: " + sBundle - + "\tDescription: " + sDescription); + + "\tAssetstore: " + iAssetstore + + "\tBundle: " + sBundle + + "\tDescription: " + sDescription); continue; // process next line in contents file } @@ -1172,6 +1172,59 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea boolean bundleExists = false; boolean permissionsExist = false; boolean descriptionExists = false; + boolean labelExists = false; + boolean heightExists = false; + boolean widthExists = false; + boolean tocExists = false; + + // look for label + String labelMarker = "\tiiif-label"; + int lMarkerIndex = line.indexOf(labelMarker); + int lEndIndex = 0; + if (lMarkerIndex > 0) { + lEndIndex = line.indexOf("\t", lMarkerIndex + 1); + if (lEndIndex == -1) { + lEndIndex = line.length(); + } + labelExists = true; + } + + // look for height + String heightMarker = "\tiiif-height"; + int hMarkerIndex = line.indexOf(heightMarker); + int hEndIndex = 0; + if (hMarkerIndex > 0) { + hEndIndex = line.indexOf("\t", hMarkerIndex + 1); + if (hEndIndex == -1) { + hEndIndex = line.length(); + } + heightExists = true; + } + + // look for width + String widthMarker = "\tiiif-width"; + int wMarkerIndex = line.indexOf(widthMarker); + int wEndIndex = 0; + if (wMarkerIndex > 0) { + wEndIndex = line.indexOf("\t", wMarkerIndex + 1); + if (wEndIndex == -1) { + wEndIndex = line.length(); + } + widthExists = true; + } + + // look for toc + String tocMarker = "\tiiif-toc"; + int tMarkerIndex = line.indexOf(tocMarker); + int tEndIndex = 0; + if (tMarkerIndex > 0) { + tEndIndex = line.indexOf("\t", tMarkerIndex + 1); + if (tEndIndex == -1) { + tEndIndex = line.length(); + } + tocExists = true; + } + // look for a bundle name String bundleMarker = "\tbundle:"; @@ -1220,18 +1273,20 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea if (bundleExists) { String bundleName = line.substring(bMarkerIndex - + bundleMarker.length(), bEndIndex).trim(); + + bundleMarker.length(), bEndIndex).trim(); processContentFileEntry(c, i, path, bitstreamName, bundleName, primary); System.out.println("\tBitstream: " + bitstreamName + - "\tBundle: " + bundleName + - primaryStr); + "\tBundle: " + bundleName + + primaryStr); } else { processContentFileEntry(c, i, path, bitstreamName, null, primary); System.out.println("\tBitstream: " + bitstreamName + primaryStr); } - if (permissionsExist || descriptionExists) { + if (permissionsExist || descriptionExists || labelExists || heightExists + || widthExists || tocExists) { + System.out.println("Gathering options."); String extraInfo = bitstreamName; if (permissionsExist) { @@ -1244,6 +1299,26 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea + line.substring(dMarkerIndex, dEndIndex); } + if (labelExists) { + extraInfo = extraInfo + + line.substring(lMarkerIndex, lEndIndex); + } + + if (heightExists) { + extraInfo = extraInfo + + line.substring(hMarkerIndex, hEndIndex); + } + + if (widthExists) { + extraInfo = extraInfo + + line.substring(wMarkerIndex, wEndIndex); + } + + if (tocExists) { + extraInfo = extraInfo + + line.substring(tMarkerIndex, tEndIndex); + } + options.add(extraInfo); } } @@ -1425,11 +1500,16 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea */ protected void processOptions(Context c, Item myItem, List options) throws SQLException, AuthorizeException { + System.out.println("Processing options."); for (String line : options) { System.out.println("\tprocessing " + line); boolean permissionsExist = false; boolean descriptionExists = false; + boolean labelExists = false; + boolean heightExists = false; + boolean widthExists = false; + boolean tocExists = false; String permissionsMarker = "\tpermissions:"; int pMarkerIndex = line.indexOf(permissionsMarker); @@ -1453,6 +1533,56 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea descriptionExists = true; } + + // look for label + String labelMarker = "\tiiif-label:"; + int lMarkerIndex = line.indexOf(labelMarker); + int lEndIndex = 0; + if (lMarkerIndex > 0) { + lEndIndex = line.indexOf("\t", lMarkerIndex + 1); + if (lEndIndex == -1) { + lEndIndex = line.length(); + } + labelExists = true; + } + + // look for height + String heightMarker = "\tiiif-height:"; + int hMarkerIndex = line.indexOf(heightMarker); + int hEndIndex = 0; + if (hMarkerIndex > 0) { + hEndIndex = line.indexOf("\t", hMarkerIndex + 1); + if (hEndIndex == -1) { + hEndIndex = line.length(); + } + heightExists = true; + } + + // look for width + String widthMarker = "\tiiif-width:"; + int wMarkerIndex = line.indexOf(widthMarker); + int wEndIndex = 0; + if (wMarkerIndex > 0) { + wEndIndex = line.indexOf("\t", wMarkerIndex + 1); + if (wEndIndex == -1) { + wEndIndex = line.length(); + } + widthExists = true; + } + + // look for toc + String tocMarker = "\tiiif-toc:"; + int tMarkerIndex = line.indexOf(tocMarker); + int tEndIndex = 0; + if (tMarkerIndex > 0) { + tEndIndex = line.indexOf("\t", tMarkerIndex + 1); + if (tEndIndex == -1) { + tEndIndex = line.length(); + } + tocExists = true; + } + + int bsEndIndex = line.indexOf("\t"); String bitstreamName = line.substring(0, bsEndIndex); @@ -1461,7 +1591,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea Group myGroup = null; if (permissionsExist) { String thisPermission = line.substring(pMarkerIndex - + permissionsMarker.length(), pEndIndex); + + permissionsMarker.length(), pEndIndex); // get permission type ("read" or "write") int pTypeIndex = thisPermission.indexOf('-'); @@ -1478,7 +1608,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea } groupName = thisPermission.substring(groupIndex + 1, - groupEndIndex); + groupEndIndex); if (thisPermission.toLowerCase().charAt(pTypeIndex + 1) == 'r') { actionID = Constants.READ; @@ -1490,7 +1620,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea myGroup = groupService.findByName(c, groupName); } catch (SQLException sqle) { System.out.println("SQL Exception finding group name: " - + groupName); + + groupName); // do nothing, will check for null group later } } @@ -1498,10 +1628,38 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea String thisDescription = ""; if (descriptionExists) { thisDescription = line.substring( - dMarkerIndex + descriptionMarker.length(), dEndIndex) + dMarkerIndex + descriptionMarker.length(), dEndIndex) .trim(); } + String thisLabel = ""; + if (labelExists) { + thisLabel = line.substring( + lMarkerIndex + labelMarker.length(), lEndIndex) + .trim(); + } + + String thisHeight = ""; + if (heightExists) { + thisHeight = line.substring( + hMarkerIndex + heightMarker.length(), hEndIndex) + .trim(); + } + + String thisWidth = ""; + if (widthExists) { + thisWidth = line.substring( + wMarkerIndex + widthMarker.length(), wEndIndex) + .trim(); + } + + String thisToc = ""; + if (tocExists) { + thisToc = line.substring( + tMarkerIndex + tocMarker.length(), tEndIndex) + .trim(); + } + Bitstream bs = null; boolean notfound = true; if (!isTest) { @@ -1518,28 +1676,62 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea if (notfound && !isTest) { // this should never happen System.out.println("\tdefault permissions set for " - + bitstreamName); + + bitstreamName); } else if (!isTest) { if (permissionsExist) { if (myGroup == null) { System.out.println("\t" + groupName - + " not found, permissions set to default"); + + " not found, permissions set to default"); } else if (actionID == -1) { System.out .println("\tinvalid permissions flag, permissions set to default"); } else { System.out.println("\tSetting special permissions for " - + bitstreamName); + + bitstreamName); setPermission(c, myGroup, actionID, bs); } } if (descriptionExists) { System.out.println("\tSetting description for " - + bitstreamName); + + bitstreamName); bs.setDescription(c, thisDescription); bitstreamService.update(c, bs); } + + if (labelExists) { + MetadataField metadataField = metadataFieldService + .findByElement(c, "iiif", "label", null); + System.out.println("\tSetting label to " + thisLabel + " in element " + + metadataField.getElement() + " on " + bitstreamName); + bitstreamService.addMetadata(c, bs, metadataField, null, thisLabel); + bitstreamService.update(c, bs); + } + + if (heightExists) { + MetadataField metadataField = metadataFieldService + .findByElement(c, "iiif", "image", "height"); + System.out.println("\tSetting height to " + thisHeight + " in element " + + metadataField.getElement() + " on " + bitstreamName); + bitstreamService.addMetadata(c, bs, metadataField, null, thisHeight); + bitstreamService.update(c, bs); + } + if (widthExists) { + MetadataField metadataField = metadataFieldService + .findByElement(c, "iiif", "image", "width"); + System.out.println("\tSetting width to " + thisWidth + " in element " + + metadataField.getElement() + " on " + bitstreamName); + bitstreamService.addMetadata(c, bs, metadataField, null, thisWidth); + bitstreamService.update(c, bs); + } + if (tocExists) { + MetadataField metadataField = metadataFieldService + .findByElement(c, "iiif", "toc", null); + System.out.println("\tSetting toc to " + thisToc + " in element " + + metadataField.getElement() + " on " + bitstreamName); + bitstreamService.addMetadata(c, bs, metadataField, null, thisToc); + bitstreamService.update(c, bs); + } } } } From a39b5097731ff550f7a023148c69d4428c54a911 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 27 Oct 2021 12:41:20 -0700 Subject: [PATCH 0491/1254] Minor changes to comments. --- .../main/java/org/dspace/app/iiif/service/ManifestService.java | 2 -- dspace/config/modules/iiif.cfg | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) 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 794fbb46ca..006054c8cf 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 @@ -175,8 +175,6 @@ public class ManifestService extends AbstractResourceService { // add the range reference to the currRange so to get an identifier currRange.addSubRange(range); - // add the range to the manifest -// manifestGenerator.addRange(range); // move the current range currRange = range; diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index d6820b5305..437b73634b 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -69,7 +69,8 @@ iiif.license.uri = dc.rights.uri # static text to be used as attribution in the iiif manifests iiif.attribution = ${dspace.name} -# URL for logo. If defined, the logo will be added to the manifest. +# URL for logo. A small image that represents an individual or organization associated with the +# resource. It is added to the IIIF manifest. iiif.logo.image = ${dspace.ui.url}/assets/images/dspace-logo.svg # (optional) one of individuals, paged or continuous. Can be overridden at the item level via From 3e0bfcc0e689ab6a787f0090b1171e0eb82a0ad3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 2 Dec 2021 11:59:39 +0100 Subject: [PATCH 0492/1254] 85277: "Feature" to determine whether you can edit your password --- .../impl/CanChangePasswordFeature.java | 45 +++++++ .../rest/AuthenticationRestControllerIT.java | 114 ++++++++++++++++++ .../app/rest/ShibbolethRestControllerIT.java | 58 ++++++++- 3 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanChangePasswordFeature.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanChangePasswordFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanChangePasswordFeature.java new file mode 100644 index 0000000000..edcb791ac8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanChangePasswordFeature.java @@ -0,0 +1,45 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization.impl; + +import java.sql.SQLException; + +import org.apache.commons.lang.StringUtils; +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.EPersonRest; +import org.dspace.core.Context; +import org.springframework.stereotype.Component; + +/** + * The canChangePassword feature. It can be used to verify if the user can change his password. + */ +@Component +@AuthorizationFeatureDocumentation(name = CanChangePasswordFeature.NAME, + description = "It can be used to verify if the user can change his password") +public class CanChangePasswordFeature implements AuthorizationFeature { + + public static final String NAME = "canChangePassword"; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + if (context.getCurrentUser() != null && StringUtils.equals(context.getAuthenticationMethod(), "password")) { + return true; + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + EPersonRest.CATEGORY + "." + EPersonRest.NAME + }; + } + +} \ No newline at end of file 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 bb75a95924..639edd5593 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 @@ -7,8 +7,11 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static java.lang.Thread.sleep; import static org.dspace.app.rest.utils.RegexUtils.REGEX_UUID; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; @@ -36,10 +39,20 @@ import javax.servlet.http.Cookie; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.authorization.Authorization; +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureService; +import org.dspace.app.rest.authorization.impl.CanChangePasswordFeature; +import org.dspace.app.rest.authorization.impl.CanCreateVersionFeature; +import org.dspace.app.rest.converter.EPersonConverter; import org.dspace.app.rest.matcher.AuthenticationStatusMatcher; +import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.HalMatcher; +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; @@ -74,6 +87,15 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio @Autowired ConfigurationService configurationService; + @Autowired + private EPersonConverter ePersonConverter; + + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + @Autowired + private Utils utils; + public static final String[] PASS_ONLY = {"org.dspace.authenticate.PasswordAuthentication"}; public static final String[] SHIB_ONLY = {"org.dspace.authenticate.ShibAuthentication"}; public static final String[] SHIB_AND_PASS = { @@ -89,9 +111,19 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio public static final String TRUSTED_IP = "7.7.7.7"; public static final String UNTRUSTED_IP = "8.8.8.8"; + private Authorization authorization; + private EPersonRest ePersonRest; + private final String feature = CanChangePasswordFeature.NAME; + + @Before public void setup() throws Exception { super.setUp(); + + AuthorizationFeature canChangePasswordFeature = authorizationFeatureService.find(CanChangePasswordFeature.NAME); + ePersonRest = ePersonConverter.convert(eperson, DefaultProjection.DEFAULT); + authorization = new Authorization(eperson, canChangePasswordFeature, ePersonRest); + // Default all tests to Password Authentication only configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", PASS_ONLY); } @@ -114,6 +146,12 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$._embedded.eperson", EPersonMatcher.matchEPersonWithGroups(admin.getEmail(), "Administrator"))); + getClient(token).perform(get("/api/authz/authorizations/" + authorization.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(authorization)))); + + getClient(token).perform(get("/api/authn/status")) .andExpect(status().isOk()) .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())); @@ -148,6 +186,11 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())); + getClient(token).perform(get("/api/authz/authorizations/" + authorization.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(authorization)))); + //Logout getClient(token).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); @@ -165,6 +208,15 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.type", is("status"))) .andExpect(header().string("WWW-Authenticate", "password realm=\"DSpace REST API\"")); + + getClient().perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); } @Test @@ -223,6 +275,15 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(cookie().doesNotExist("DSPACE-XSRF-COOKIE")) .andExpect(header().doesNotExist("DSPACE-XSRF-TOKEN")); + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + // To complete the authentication process, we pass our auth cookie to the "/login" endpoint. // This is where the temporary cookie will be read, verified & destroyed. After this point, the UI will // only use the 'Authorization' header for all future requests. @@ -251,6 +312,15 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))) .andExpect(jsonPath("$.type", is("status"))); + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + //Logout, invalidating the token getClient(token).perform(post("/api/authn/logout").header("Origin", uiURL)) .andExpect(status().isNoContent()); @@ -277,6 +347,15 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(false))) .andExpect(jsonPath("$.authenticationMethod").doesNotExist()); + + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); } // NOTE: This test is similar to testStatusShibAuthenticatedWithCookie(), but proves the same process works @@ -466,6 +545,15 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticated", is(false))) .andExpect(jsonPath("$.authenticationMethod").doesNotExist()) .andExpect(jsonPath("$.type", is("status"))); + + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); } @Test @@ -530,6 +618,11 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticationMethod", is("password"))) .andExpect(jsonPath("$.type", is("status"))); + getClient(newToken).perform(get("/api/authz/authorizations/" + authorization.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(authorization)))); + // Logout, invalidating token getClient(token).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); @@ -877,6 +970,14 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$._embedded.eperson", EPersonMatcher.matchEPersonWithGroups(eperson.getEmail(), "Anonymous", "Reviewers"))); + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); // Logout, invalidating token getClient(token).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); @@ -942,6 +1043,15 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$._embedded.eperson", EPersonMatcher.matchEPersonWithGroups(eperson.getEmail(), "Anonymous"))); + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + // Logout, invalidating token getClient(token).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); @@ -973,6 +1083,10 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticationMethod", is("password"))) .andExpect(jsonPath("$.type", is("status"))); + getClient(token).perform(get("/api/authz/authorizations/" + authorization.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(authorization)))); //Logout getClient(token).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java index c9fe711e0b..9e3ed6cabb 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java @@ -13,8 +13,20 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.dspace.app.rest.authorization.Authorization; +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureService; +import org.dspace.app.rest.authorization.impl.CanChangePasswordFeature; +import org.dspace.app.rest.authorization.impl.CanCreateVersionFeature; +import org.dspace.app.rest.converter.EPersonConverter; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; import org.dspace.services.ConfigurationService; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,12 +41,28 @@ public class ShibbolethRestControllerIT extends AbstractControllerIntegrationTes @Autowired ConfigurationService configurationService; + @Autowired + private EPersonConverter ePersonConverter; + + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + @Autowired + private Utils utils; + public static final String[] PASS_ONLY = {"org.dspace.authenticate.PasswordAuthentication"}; public static final String[] SHIB_ONLY = {"org.dspace.authenticate.ShibAuthentication"}; + private EPersonRest ePersonRest; + private final String feature = CanChangePasswordFeature.NAME; + @Before public void setup() throws Exception { super.setUp(); + + AuthorizationFeature canChangePasswordFeature = authorizationFeatureService.find(CanChangePasswordFeature.NAME); + ePersonRest = ePersonConverter.convert(eperson, DefaultProjection.DEFAULT); + // Add a second trusted host for some tests configurationService.setProperty("rest.cors.allowed-origins", "${dspace.ui.url}, http://anotherdspacehost:4000"); @@ -59,15 +87,41 @@ public class ShibbolethRestControllerIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(true))) .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } @Test public void testRedirectToGivenTrustedUrl() throws Exception { - getClient().perform(get("/api/authn/shibboleth") + String token = getClient().perform(get("/api/authn/shibboleth") .param("redirectUrl", "http://localhost:8080/server/api/authn/status") .requestAttr("SHIB-MAIL", eperson.getEmail())) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost:8080/server/api/authn/status")); + .andExpect(redirectedUrl("http://localhost:8080/server/api/authn/status")) + .andReturn().getResponse().getHeader("Authorization"); + + getClient(token).perform(get("/api/authn/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + + getClient(token).perform( + get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } @Test From b430db37907379cbfa9a02c85a90cc285c4d5b1f Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 2 Dec 2021 12:46:00 +0100 Subject: [PATCH 0493/1254] 85276: Fix checkstyle issues85276: Fix checkstyle issues85276: Fix checkstyle issues85276: Fix checkstyle issues85276: Fix checkstyle issues85276: Fix checkstyle issues85276: Fix checkstyle issues85276: Fix checkstyle issues --- .../rest/authorization/impl/CanChangePasswordFeature.java | 2 +- .../org/dspace/app/rest/AuthenticationRestControllerIT.java | 5 ----- .../java/org/dspace/app/rest/ShibbolethRestControllerIT.java | 5 ----- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanChangePasswordFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanChangePasswordFeature.java index edcb791ac8..ebeb6714c0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanChangePasswordFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanChangePasswordFeature.java @@ -38,7 +38,7 @@ public class CanChangePasswordFeature implements AuthorizationFeature { @Override public String[] getSupportedTypes() { return new String[]{ - EPersonRest.CATEGORY + "." + EPersonRest.NAME + EPersonRest.CATEGORY + "." + EPersonRest.NAME }; } 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 639edd5593..adb1426e8b 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 @@ -7,11 +7,8 @@ */ package org.dspace.app.rest; -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static java.lang.Thread.sleep; import static org.dspace.app.rest.utils.RegexUtils.REGEX_UUID; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; @@ -43,7 +40,6 @@ import org.dspace.app.rest.authorization.Authorization; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureService; import org.dspace.app.rest.authorization.impl.CanChangePasswordFeature; -import org.dspace.app.rest.authorization.impl.CanCreateVersionFeature; import org.dspace.app.rest.converter.EPersonConverter; import org.dspace.app.rest.matcher.AuthenticationStatusMatcher; import org.dspace.app.rest.matcher.AuthorizationMatcher; @@ -150,7 +146,6 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( AuthorizationMatcher.matchAuthorization(authorization)))); - getClient(token).perform(get("/api/authn/status")) .andExpect(status().isOk()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java index 9e3ed6cabb..4b8ee056e0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java @@ -13,20 +13,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.dspace.app.rest.authorization.Authorization; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureService; import org.dspace.app.rest.authorization.impl.CanChangePasswordFeature; -import org.dspace.app.rest.authorization.impl.CanCreateVersionFeature; import org.dspace.app.rest.converter.EPersonConverter; -import org.dspace.app.rest.converter.ItemConverter; -import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; import org.dspace.services.ConfigurationService; -import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; From d2eee535f071b1f227e224e3ce564a15c2350b94 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 2 Dec 2021 13:16:05 +0100 Subject: [PATCH 0494/1254] 85149: Fix Sword password login --- .../java/org/dspace/authenticate/PasswordAuthentication.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java index f3f79151ff..c7687d1a9e 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java @@ -219,7 +219,9 @@ public class PasswordAuthentication .checkPassword(context, eperson, password)) { // login is ok if password matches: context.setCurrentUser(eperson); - request.getSession().setAttribute(PASSWORD_AUTHENTICATED, true); + if (request != null) { + request.getSession().setAttribute(PASSWORD_AUTHENTICATED, true); + } log.info(LogHelper.getHeader(context, "authenticate", "type=PasswordAuthentication")); return SUCCESS; } else { From b4249dbc0d42b594bf6a212441b0fa87d965be3d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 9 Nov 2021 15:04:05 -0600 Subject: [PATCH 0495/1254] Ensure "Bearer " prefix always removed from Authorization header --- .../rest/AuthenticationRestControllerIT.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 7584874668..8a94aa3ccf 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 @@ -231,7 +231,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // Verify the Auth cookie has been destroyed .andExpect(cookie().value(AUTHORIZATION_COOKIE, "")) // Verify token is now sent back in the Authorization header as the Bearer token - .andExpect(header().string(AUTHORIZATION_HEADER, "Bearer " + token)) + .andExpect(header().string(AUTHORIZATION_HEADER, AUTHORIZATION_TYPE + token)) // Verify that the CSRF token has been changed // (as both cookie and header should be sent back) .andExpect(cookie().exists("DSPACE-XSRF-COOKIE")) @@ -276,9 +276,6 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // Login via password to retrieve a valid token String token = getAuthToken(eperson.getEmail(), password); - // Remove "Bearer " from that token, so that we are left with the token itself - token = token.replace("Bearer ", ""); - // Fake the creation of an auth cookie, just for testing. (Currently, it's not possible to create an auth cookie // via Password auth, but this test proves it would work if enabled) Cookie authCookie = new Cookie(AUTHORIZATION_COOKIE, token); @@ -306,7 +303,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // Verify the Auth cookie has been destroyed .andExpect(cookie().value(AUTHORIZATION_COOKIE, "")) // Verify token is now sent back in the Authorization header - .andExpect(header().string(AUTHORIZATION_HEADER, "Bearer " + token)) + .andExpect(header().string(AUTHORIZATION_HEADER, AUTHORIZATION_TYPE + token)) // Verify that the CSRF token has been changed // (as both cookie and header should be sent back) .andExpect(cookie().exists("DSPACE-XSRF-COOKIE")) @@ -506,7 +503,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(cookie().value("DSPACE-XSRF-COOKIE", "")) // CSRF Tokens generated by Spring Security are UUIDs .andExpect(header().string("DSPACE-XSRF-TOKEN", matchesPattern(REGEX_UUID))) - .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER); + .andReturn().getResponse() + .getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); assertNotEquals(token, newToken); @@ -529,9 +527,6 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // Login via password to retrieve a valid token String token = getAuthToken(eperson.getEmail(), password); - // Remove "Bearer " from that token, so that we are left with the token itself - token = token.replace("Bearer ", ""); - // Save token to an Authorization cookie Cookie[] cookies = new Cookie[1]; cookies[0] = new Cookie(AUTHORIZATION_COOKIE, token); @@ -565,7 +560,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // (as both cookie and header should be sent back) .andExpect(cookie().exists("DSPACE-XSRF-COOKIE")) .andExpect(header().exists("DSPACE-XSRF-TOKEN")) - .andReturn().getResponse().getHeader("Authorization"); + .andReturn().getResponse() + .getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); //Logout getClient(token).perform(post("/api/authn/logout")) @@ -850,7 +846,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .requestAttr("SHIB-MAIL", eperson.getEmail()) .requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff")) .andExpect(status().isOk()) - .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER); + .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); getClient(token).perform(get("/api/authn/status").param("projection", "full")) .andExpect(status().isOk()) @@ -891,7 +887,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .with(ip("123.123.123.123")) .header("SHIB-MAIL", eperson.getEmail())) .andExpect(status().isOk()) - .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER); + .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); getClient(token).perform(get("/api/authn/status").param("projection", "full") .with(ip("123.123.123.123"))) @@ -911,7 +907,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .with(ip("234.234.234.234")) .header("SHIB-MAIL", eperson.getEmail())) .andExpect(status().isOk()) - .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER); + .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); getClient(token).perform(get("/api/authn/status").param("projection", "full") .with(ip("234.234.234.234"))) @@ -972,7 +968,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .requestAttr("SHIB-MAIL", eperson.getEmail()) .requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff")) .andExpect(status().isOk()) - .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace("Bearer ", ""); + .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); //Check if we have a valid token getClient(token).perform(get("/api/authn/status")) @@ -1064,7 +1060,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .requestAttr("SHIB-MAIL", eperson.getEmail()) .requestAttr("SHIB-SCOPED-AFFILIATION", "faculty;staff")) .andExpect(status().isOk()) - .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER); + .andReturn().getResponse().getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); //Logout getClient(token).perform(post("/api/authn/logout")) From 1146d6ec140ed606c9d143a29681ed2f8f7495f9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 11 Nov 2021 12:00:12 -0600 Subject: [PATCH 0496/1254] Fix unstable tests by checking for JWT equality claim by claim, since a one second difference in expiration date can change entire JWT. --- .../rest/AuthenticationRestControllerIT.java | 113 +++++++++++++++--- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 8a94aa3ccf..69681fec54 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 @@ -18,6 +18,7 @@ import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -29,11 +30,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.text.ParseException; import java.util.Base64; import java.util.Map; import javax.servlet.http.Cookie; import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.dspace.app.rest.matcher.AuthenticationStatusMatcher; @@ -224,22 +228,30 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // only use the 'Authorization' header for all future requests. // (NOTE that this call has an "Origin" matching the UI, to better mock that the request came from there & // to verify the temporary auth cookie is valid for the UI's origin.) - getClient().perform(post("/api/authn/login").header("Origin", uiURL) - .secure(true) - .cookie(authCookie)) + String headerToken = getClient().perform(post("/api/authn/login").header("Origin", uiURL) + .secure(true) + .cookie(authCookie)) .andExpect(status().isOk()) // Verify the Auth cookie has been destroyed .andExpect(cookie().value(AUTHORIZATION_COOKIE, "")) - // Verify token is now sent back in the Authorization header as the Bearer token - .andExpect(header().string(AUTHORIZATION_HEADER, AUTHORIZATION_TYPE + token)) + // Verify Authorization header is returned + .andExpect(header().exists(AUTHORIZATION_HEADER)) // Verify that the CSRF token has been changed // (as both cookie and header should be sent back) .andExpect(cookie().exists("DSPACE-XSRF-COOKIE")) - .andExpect(header().exists("DSPACE-XSRF-TOKEN")); + .andExpect(header().exists("DSPACE-XSRF-TOKEN")) + .andReturn().getResponse() + .getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); + + // Verify that the token in the returned header has the *same claims* as the auth Cookie token + // NOTE: We test claim equality because the token's expiration date may change during this request. If it does + // change, then the tokens will look different even though the claims are the same. + assertTrue("Check tokens " + token + " and " + headerToken + " have same claims", + tokenClaimsEqual(token, headerToken)); // Now that the auth cookie is cleared, all future requests (from UI) // should be made via the Authorization header. So, this tests the token is still valid if sent via header. - getClient(token).perform(get("/api/authn/status").header("Origin", uiURL)) + getClient(headerToken).perform(get("/api/authn/status").header("Origin", uiURL)) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) @@ -247,7 +259,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.type", is("status"))); //Logout, invalidating the token - getClient(token).perform(post("/api/authn/logout").header("Origin", uiURL)) + getClient(headerToken).perform(post("/api/authn/logout").header("Origin", uiURL)) .andExpect(status().isNoContent()); } @@ -298,20 +310,28 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio // To complete the authentication process, we pass our auth cookie to the "/login" endpoint. // This is where the temporary cookie will be read, verified & destroyed. After this point, the UI will // only use the Authorization header for all future requests. - getClient().perform(post("/api/authn/login").secure(true).cookie(authCookie)) + String headerToken = getClient().perform(post("/api/authn/login").secure(true).cookie(authCookie)) .andExpect(status().isOk()) // Verify the Auth cookie has been destroyed .andExpect(cookie().value(AUTHORIZATION_COOKIE, "")) - // Verify token is now sent back in the Authorization header - .andExpect(header().string(AUTHORIZATION_HEADER, AUTHORIZATION_TYPE + token)) + // Verify Authorization header is returned + .andExpect(header().exists(AUTHORIZATION_HEADER)) // Verify that the CSRF token has been changed // (as both cookie and header should be sent back) .andExpect(cookie().exists("DSPACE-XSRF-COOKIE")) - .andExpect(header().exists("DSPACE-XSRF-TOKEN")); + .andExpect(header().exists("DSPACE-XSRF-TOKEN")) + .andReturn().getResponse() + .getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); + + // Verify that the token in the returned header has the *same claims* as the auth Cookie token + // NOTE: We test claim equality because the token's expiration date may change during this request. If it does + // change, then the tokens will look different even though the claims are the same. + assertTrue("Check tokens " + token + " and " + headerToken + " have same claims", + tokenClaimsEqual(token, headerToken)); // Now that the auth cookie is cleared, all future requests (from UI) // should be made via the Authorization header. So, this tests the token is still valid if sent via header. - getClient(token).perform(get("/api/authn/status")) + getClient(headerToken).perform(get("/api/authn/status")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.okay", is(true))) @@ -319,7 +339,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.type", is("status"))); // Logout, invalidating the token - getClient(token).perform(post("/api/authn/logout")) + getClient(headerToken).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); } @@ -327,13 +347,20 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio public void testTwoAuthenticationTokens() throws Exception { String token1 = getAuthToken(eperson.getEmail(), password); - //Sleep so tokens are different + // Sleep for >1sec ensures the tokens are different. Because expiration date in the token includes a timestamp, + // waiting for >1sec will result in a different expiration, and therefore a slightly different token. sleep(1200); String token2 = getAuthToken(eperson.getEmail(), password); + // Tokens should be different assertNotEquals(token1, token2); + // However, tokens should contain the same claims + assertTrue("Check tokens " + token1 + " and " + token2 + " have same claims", + tokenClaimsEqual(token1, token2)); + + // BOTH tokens should be valid, as they represent the same authenticated user's "session". getClient(token1).perform(get("/api/authn/status").param("projection", "full")) .andExpect(status().isOk()) @@ -364,6 +391,7 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio EPersonMatcher.matchEPersonOnEmail(eperson.getEmail()))); // Logout, this will invalidate both tokens + // NOTE: invalidation of both tokens is tested in testLogoutInvalidatesAllTokens() getClient(token1).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); @@ -457,7 +485,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio public void testLogoutInvalidatesAllTokens() throws Exception { String token1 = getAuthToken(eperson.getEmail(), password); - //Sleep so tokens are different + // Sleep for >1sec ensures the tokens are different. Because expiration date in the token includes a timestamp, + // waiting for >1sec will result in a different expiration, and therefore a slightly different token. sleep(1200); String token2 = getAuthToken(eperson.getEmail(), password); @@ -486,7 +515,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio public void testRefreshToken() throws Exception { String token = getAuthToken(eperson.getEmail(), password); - //Sleep so tokens are different + // Sleep for >1sec ensures the tokens are different. Because expiration date in the token includes a timestamp, + // waiting for >1sec will result in a different expiration, and therefore a slightly different token. sleep(1200); String newToken = getClient(token).perform(post("/api/authn/login")) @@ -506,8 +536,14 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andReturn().getResponse() .getHeader(AUTHORIZATION_HEADER).replace(AUTHORIZATION_TYPE, ""); + // Tokens should be different assertNotEquals(token, newToken); + // However, tokens should contain the same claims + assertTrue("Check tokens " + token + " and " + newToken + " have same claims", + tokenClaimsEqual(token, newToken)); + + // Verify new token is valid getClient(newToken).perform(get("/api/authn/status")) .andExpect(status().isOk()) @@ -515,7 +551,8 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.authenticated", is(true))) .andExpect(jsonPath("$.type", is("status"))); - // Logout, invalidating new token + // Logout, this will invalidate both tokens + // NOTE: invalidation of both tokens is tested in testLogoutInvalidatesAllTokens() getClient(newToken).perform(post("/api/authn/logout")) .andExpect(status().isNoContent()); } @@ -1385,5 +1422,45 @@ public class AuthenticationRestControllerIT extends AbstractControllerIntegratio return bitstream; } + + /** + * Check if the claims (except for expiration date) are equal between two JWTs. + * Expiration date (exp) claim is ignored as it includes a timestamp and therefore changes every second. + * So, this method checks to ensure token equality by comparing all other claims. + * + * @param token1 first token + * @param token2 second token + * @return True if tokens are identical or have the same claims (ignoring "exp"). False otherwise. + */ + private boolean tokenClaimsEqual(String token1, String token2) { + // First check for exact match tokens. These are guaranteed to have equal claims. + if (token1.equals(token2)) { + return true; + } + + // Now parse the two tokens and compare the claims in each + try { + SignedJWT jwt1 = SignedJWT.parse(token1); + SignedJWT jwt2 = SignedJWT.parse(token2); + JWTClaimsSet jwt1ClaimsSet = jwt1.getJWTClaimsSet(); + JWTClaimsSet jwt2ClaimsSet = jwt2.getJWTClaimsSet(); + + Map jwt1Claims = jwt1ClaimsSet.getClaims(); + for (String claim: jwt1Claims.keySet()) { + // Ignore the "exp" (expiration date) claim, as it includes a timestamp and changes every second + if (claim.equals("exp")) { + continue; + } + // If one other claim is not equal, return false + if (!jwt1ClaimsSet.getClaim(claim).equals(jwt2ClaimsSet.getClaim(claim))) { + return false; + } + } + // all claims (except "exp") are equal! + return true; + } catch (ParseException e) { + return false; + } + } } From 4c87bded7dc19b652eba3f154cfea7843ce980ed Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 5 Nov 2021 17:32:56 -0500 Subject: [PATCH 0497/1254] Fix test logging so that it also uses Log4j2 & sends logs to failsafe, etc --- .../AbstractControllerIntegrationTest.java | 7 +++ .../AbstractWebClientIntegrationTest.java | 7 +++ .../test/LoggingTestExecutionListener.java | 44 ++++++++++++++ .../resources/application-test.properties | 17 ++++++ .../src/test/resources/log4j.properties | 58 ------------------- .../src/test/resources/log4j2-test.xml | 26 +++++++++ .../src/test/resources/logback.xml | 12 ---- 7 files changed, 101 insertions(+), 70 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/test/LoggingTestExecutionListener.java create mode 100644 dspace-server-webapp/src/test/resources/application-test.properties delete mode 100644 dspace-server-webapp/src/test/resources/log4j.properties create mode 100644 dspace-server-webapp/src/test/resources/log4j2-test.xml delete mode 100644 dspace-server-webapp/src/test/resources/logback.xml diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java index b9839d15e9..a08fd625c4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -38,6 +38,8 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; @@ -70,6 +72,11 @@ import org.springframework.web.context.WebApplicationContext; @ContextConfiguration(initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class }) // Tell Spring to make ApplicationContext an instance of WebApplicationContext (for web-based tests) @WebAppConfiguration +// Load our src/test/resources/application-test.properties to override some settings in default application.properties +@TestPropertySource(locations = "classpath:application-test.properties") +// Enable our custom Logging listener to log when each test method starts/stops +@TestExecutionListeners(listeners = {LoggingTestExecutionListener.class}, + mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWithDatabase { protected static final String AUTHORIZATION_HEADER = "Authorization"; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java index 9083887581..6556624c6b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -21,6 +21,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.http.HttpEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; /** @@ -47,6 +49,11 @@ import org.springframework.test.context.junit4.SpringRunner; @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // Load DSpace initializers in Spring ApplicationContext (to initialize DSpace Kernel & Configuration) @ContextConfiguration(initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class }) +// Load our src/test/resources/application-test.properties to override some settings in default application.properties +@TestPropertySource(locations = "classpath:application-test.properties") +// Enable our custom Logging listener to log when each test method starts/stops +@TestExecutionListeners(listeners = {LoggingTestExecutionListener.class}, + mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWithDatabase { // (Random) port chosen for test web server @LocalServerPort diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/LoggingTestExecutionListener.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/LoggingTestExecutionListener.java new file mode 100644 index 0000000000..2a04bab204 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/LoggingTestExecutionListener.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.test; + +import org.apache.logging.log4j.Logger; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.AbstractTestExecutionListener; + +/** + * Custom DSpace TestExecutionListener which logs messages whenever a specific Test Case (i.e. test method) has + * started or ended execution. This makes Test environment logs easier to read/understand as you know which method has + * caused errors, etc. + */ +public class LoggingTestExecutionListener extends AbstractTestExecutionListener { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggingTestExecutionListener.class); + + /** + * Before each test method is run + * @param testContext + */ + @Override + public void beforeTestMethod(TestContext testContext) { + // Log the test method being executed. Put lines around it to make it stand out. + log.info("---"); + log.info("Starting execution of test method: {}()", testContext.getTestMethod().getName()); + log.info("---"); + } + + /** + * After each test method is run + * @param testContext + */ + @Override + public void afterTestMethod(TestContext testContext) { + // Log the test method just completed. + log.info("Finished execution of test method: {}()", testContext.getTestMethod().getName()); + } +} diff --git a/dspace-server-webapp/src/test/resources/application-test.properties b/dspace-server-webapp/src/test/resources/application-test.properties new file mode 100644 index 0000000000..9a396cf8e5 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/application-test.properties @@ -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/ +# + +# +# application-test.properties contains our Spring Boot configuration for TEST environment +# +# By default, our Spring Boot TEST environment uses the src/main/resources/application.properties file. +# However, this file can be used to override specific application.properties settings for the test environment. + +## Log4j2 configuration for test environment +## This file is found on classpath at src/test/resources/log4j2-test.xml +logging.config = classpath:log4j2-test.xml \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/log4j.properties b/dspace-server-webapp/src/test/resources/log4j.properties deleted file mode 100644 index 2797b7c655..0000000000 --- a/dspace-server-webapp/src/test/resources/log4j.properties +++ /dev/null @@ -1,58 +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/ -# -########################################################################### -# -# log4j.properties -# -# -########################################################################### - -# This is a copy of the log4j configuration file for DSpace, to avoid -# getting errors when running tests. - -# Set root category priority to INFO and its only appender to A1. -log4j.rootCategory=INFO, A1 - -# A1 is set to be a ConsoleAppender. -log4j.appender.A1=org.apache.log4j.ConsoleAppender - -# A1 uses PatternLayout. -log4j.appender.A1.layout=org.apache.logging.log4j.PatternLayout -log4j.appender.A1.layout.ConversionPattern=%d %-5p %c @ %m%n - -########################################################################### -# Other settings -########################################################################### - -# Block passwords from being exposed in Axis logs. -# (DEBUG exposes passwords in Basic Auth) -log4j.logger.org.apache.axis.handlers.http.HTTPAuthHandler=INFO - -# Block services logging except on exceptions -log4j.logger.org.dspace.kernel=ERROR -log4j.logger.org.dspace.services=ERROR -log4j.logger.org.dspace.servicemanager=ERROR -log4j.logger.org.dspace.providers=ERROR -log4j.logger.org.dspace.utils=ERROR - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.Target=System.out -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n -# -# Root logger option -log4j.rootLogger=INFO, stdout - -# Hibernate logging options (INFO only shows startup messages) -log4j.logger.org.hibernate=INFO - -# For detailed Hibernate logging in Unit Tests, you can enable the following -# setting which logs all JDBC bind parameter runtime arguments. -# This will drastically increase the size of Unit Test logs though. -#log4j.logger.org.hibernate.SQL=DEBUG, A1 -#log4j.logger.org.hibernate.type=TRACE, A1 diff --git a/dspace-server-webapp/src/test/resources/log4j2-test.xml b/dspace-server-webapp/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..e9a2d5ef30 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/log4j2-test.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/test/resources/logback.xml b/dspace-server-webapp/src/test/resources/logback.xml deleted file mode 100644 index 8455a00b53..0000000000 --- a/dspace-server-webapp/src/test/resources/logback.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - From 84bdb8bc20a261f86b90b91762852b4ae55c2a03 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 5 Nov 2021 17:33:33 -0500 Subject: [PATCH 0498/1254] Fix incorrect comments --- dspace/config/log4j2.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace/config/log4j2.xml b/dspace/config/log4j2.xml index 955341507d..eeab2bc9c6 100644 --- a/dspace/config/log4j2.xml +++ b/dspace/config/log4j2.xml @@ -76,7 +76,7 @@ - # Block services logging except on exceptions + - # Block passwords from being exposed in Axis logs. - # (DEBUG exposes passwords in Basic Auth) + From 1c31a3a7c53d2aac9a061858973b9598b5683504 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 8 Nov 2021 14:02:42 -0600 Subject: [PATCH 0499/1254] Add debug logging to auth process --- .../rest/security/EPersonRestAuthenticationProvider.java | 2 ++ .../dspace/app/rest/security/ShibbolethLoginFilter.java | 2 ++ .../app/rest/security/StatelessAuthenticationFilter.java | 7 ++++--- .../org/dspace/app/rest/security/StatelessLoginFilter.java | 1 + .../org/dspace/app/rest/security/jwt/JWTTokenHandler.java | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java index 95e504ad3b..20844ec946 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java @@ -68,9 +68,11 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider // If a user already exists in the context, then no authentication is necessary. User is already logged in if (context != null && context.getCurrentUser() != null) { // Simply refresh/reload the auth token. If token has expired, the token will change. + log.debug("Request to refresh auth token"); return authenticateRefreshTokenRequest(context); } else { // Otherwise, this is a new login & we need to attempt authentication + log.debug("Request to authenticate new login"); return authenticateNewLogin(authentication); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java index 9c78f9e3ad..e10fc49ecc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ShibbolethLoginFilter.java @@ -86,6 +86,8 @@ public class ShibbolethLoginFilter extends StatelessLoginFilter { // Once we've gotten here, we know we have a successful login (i.e. attemptAuthentication() succeeded) DSpaceAuthentication dSpaceAuthentication = (DSpaceAuthentication) auth; + log.debug("Shib authentication successful for EPerson {}. Sending back temporary auth cookie", + dSpaceAuthentication.getName()); // OVERRIDE DEFAULT behavior of StatelessLoginFilter to return a temporary authentication cookie containing // the Auth Token (JWT). This Cookie is required because we *redirect* the user back to the client/UI after // a successful Shibboleth login. Headers cannot be sent via a redirect, so a Cookie must be sent to provide diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java index 7309f579a6..964d35f42c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java @@ -120,12 +120,11 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { throws AuthorizeException, SQLException { if (restAuthenticationService.hasAuthenticationData(request)) { - // parse the token. - Context context = ContextUtil.obtainContext(request); - + // parse the token. EPerson eperson = restAuthenticationService.getAuthenticatedEPerson(request, res, context); if (eperson != null) { + log.debug("Found authentication data in request for EPerson {}", eperson.getEmail()); //Pass the eperson ID to the request service requestService.setCurrentUserId(eperson.getID()); @@ -174,6 +173,8 @@ public class StatelessAuthenticationFilter extends BasicAuthenticationFilter { if (!authorizeService.isAdmin(context, onBehalfOfEPerson)) { requestService.setCurrentUserId(epersonUuid); context.switchContextUser(onBehalfOfEPerson); + log.debug("Found 'on-behalf-of' authentication data in request for EPerson {}", + onBehalfOfEPerson.getEmail()); return new DSpaceAuthentication(onBehalfOfEPerson, authenticationProvider.getGrantedAuthorities(context)); } else { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java index 8a84c5b2cc..c95fce71c4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessLoginFilter.java @@ -97,6 +97,7 @@ public class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter Authentication auth) throws IOException, ServletException { DSpaceAuthentication dSpaceAuthentication = (DSpaceAuthentication) auth; + log.debug("Authentication successful for EPerson {}", dSpaceAuthentication.getName()); restAuthenticationService.addAuthenticationDataForUser(req, res, dSpaceAuthentication, false); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java index 91da4b0ea1..6beab1aa85 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/jwt/JWTTokenHandler.java @@ -410,7 +410,7 @@ public abstract class JWTTokenHandler { if (StringUtils.isBlank(ePerson.getSessionSalt()) || previousLoginDate == null || (ePerson.getLastActive().getTime() - previousLoginDate.getTime() > getExpirationPeriod())) { - + log.debug("Regenerating auth token as session salt was either empty or expired.."); ePerson.setSessionSalt(generateRandomKey()); ePersonService.update(context, ePerson); } From db1a520e3dcb7c918c066d41c403cb4946de0246 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 9 Nov 2021 15:08:12 -0600 Subject: [PATCH 0500/1254] Add sample debug logging for REST API --- dspace-server-webapp/src/test/resources/log4j2-test.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-server-webapp/src/test/resources/log4j2-test.xml b/dspace-server-webapp/src/test/resources/log4j2-test.xml index e9a2d5ef30..5cb686e94f 100644 --- a/dspace-server-webapp/src/test/resources/log4j2-test.xml +++ b/dspace-server-webapp/src/test/resources/log4j2-test.xml @@ -17,6 +17,8 @@ + + From ec9d81b218de9feaca3f84fb98a6251c0d764c4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 21:04:45 +0000 Subject: [PATCH 0501/1254] Bump google-oauth-client from 1.23.0 to 1.32.1 Bumps [google-oauth-client](https://github.com/googleapis/google-oauth-java-client) from 1.23.0 to 1.32.1. - [Release notes](https://github.com/googleapis/google-oauth-java-client/releases) - [Changelog](https://github.com/googleapis/google-oauth-java-client/blob/master/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-oauth-java-client/compare/1.23.0...v1.32.1) --- updated-dependencies: - dependency-name: com.google.oauth-client:google-oauth-client 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 08702a4a09..0502eea5b0 100644 --- a/pom.xml +++ b/pom.xml @@ -1688,7 +1688,7 @@ com.google.oauth-client google-oauth-client - 1.23.0 + 1.32.1 From 3559be50c46ab0a78166387c4d08f5cb2538c5f1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 2 Dec 2021 16:30:04 -0800 Subject: [PATCH 0502/1254] Added collection metadata check for iiif enabled. --- .../org/dspace/app/iiif/service/utils/IIIFUtils.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 11e82a3f2f..79c844948f 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 @@ -93,16 +93,20 @@ public class IIIFUtils { } /** - * This method verify if the IIIF feature is enabled on the item + * This method verify if the IIIF feature is enabled on the item or parent collection. * * @param item the dspace item * @return true if the item supports IIIF */ public boolean isIIIFEnabled(Item item) { - return item.getMetadata().stream() + return item.getOwningCollection().getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")) + || item.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")); + m.getValue().equalsIgnoreCase("yes")); } /** From 6e5b9634a382fa7c74511a31a49258a7f8eaf330 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 3 Dec 2021 13:31:53 +0100 Subject: [PATCH 0503/1254] implemented feedback endpoint --- .../DSpaceFeedbackNotFoundException.java | 30 +++++ .../dspace/app/rest/model/FeedbackRest.java | 75 ++++++++++++ .../repository/FeedbackRestRepository.java | 112 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceFeedbackNotFoundException.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceFeedbackNotFoundException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceFeedbackNotFoundException.java new file mode 100644 index 0000000000..15878e857c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceFeedbackNotFoundException.java @@ -0,0 +1,30 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * When a request is malformed, we use this exception to indicate this to the client + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Not Found") +public class DSpaceFeedbackNotFoundException extends RuntimeException { + + private static final long serialVersionUID = 4631940402294095433L; + + public DSpaceFeedbackNotFoundException(String message) { + this(message, null); + } + + public DSpaceFeedbackNotFoundException(String message, Exception e) { + super(message, e); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java new file mode 100644 index 0000000000..5ab04aa062 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.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.model; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; + +/** + * The REST object for the Feedback objects + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class FeedbackRest extends BaseObjectRest { + + private static final long serialVersionUID = 1L; + + public static final String NAME = "feedback"; + public static final String CATEGORY = RestAddressableModel.CORE; + + private String page; + private String email; + private String message; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + @Override + @JsonIgnore + public Integer getId() { + return id; + } + + @Override + @JsonIgnore + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @SuppressWarnings("rawtypes") + public Class getController() { + return RestResourceController.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java new file mode 100644 index 0000000000..c0e24e549b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.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.app.rest.repository; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Date; +import java.util.Objects; +import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang.StringUtils; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.DSpaceFeedbackNotFoundException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.FeedbackRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the Repository that takes care of the operations on the {@link FeedbackRest} objects + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +@Component(FeedbackRest.CATEGORY + "." + FeedbackRest.NAME) +public class FeedbackRestRepository extends DSpaceRestRepository { + + @Autowired + private ConfigurationService configurationService; + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(FeedbackRest.NAME, "findAll"); + } + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public FeedbackRest findOne(Context context, Integer id) { + throw new RepositoryMethodNotImplementedException(FeedbackRest.NAME, "findOne"); + } + + @Override + @PreAuthorize("permitAll()") + protected FeedbackRest createAndReturn(Context context) throws AuthorizeException, SQLException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + ObjectMapper mapper = new ObjectMapper(); + FeedbackRest feedbackRest = null; + + String recipientEmail = configurationService.getProperty("feedback.recipient"); + if (StringUtils.isBlank(recipientEmail)) { + throw new DSpaceFeedbackNotFoundException("Recipient's email was not found!"); + } + + try { + feedbackRest = mapper.readValue(req.getInputStream(), FeedbackRest.class); + } catch (IOException exIO) { + throw new UnprocessableEntityException("error parsing the body " + exIO.getMessage(), exIO); + } + + String session = req.getSession().getId(); + String agent = req.getHeader("User-Agent"); + String currentUserEmail = StringUtils.EMPTY; + + if (Objects.nonNull(context.getCurrentUser())) { + currentUserEmail = context.getCurrentUser().getEmail(); + } + + String senderEmail = feedbackRest.getEmail(); + String message = feedbackRest.getMessage(); + String page = feedbackRest.getPage(); + + if (StringUtils.isBlank(senderEmail) || StringUtils.isBlank(message)) { + throw new DSpaceBadRequestException("Filds as e-mail and message are mandatory!"); + } + + try { + Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), "feedback")); + email.addArgument(new Date()); // Date + email.addArgument(senderEmail); // Email + email.addArgument(currentUserEmail); // Logged in as + email.addArgument(page); // Referring page + email.addArgument(agent); // User agent + email.addArgument(session); // Session ID + email.addArgument(message); // The feedback itself + email.send(); + } catch (IOException | MessagingException e) { + throw new RuntimeException(e.getMessage(), e); + } + return null; + } + + @Override + public Class getDomainClass() { + return FeedbackRest.class; + } + +} \ No newline at end of file From 61bbf3840dae076307209554fbfc0ee1917bb527 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 3 Dec 2021 13:32:12 +0100 Subject: [PATCH 0504/1254] added tests --- .../app/rest/FeedbackRestRepositoryIT.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java new file mode 100644 index 0000000000..496b52fb6f --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java @@ -0,0 +1,89 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; +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.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.model.FeedbackRest; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test class for the feedback endpoint + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class FeedbackRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + @Test + public void findAllTest() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/core/feedbacks")) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void findOneTest() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/core/feedbacks/1")) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void sendFeedbackTest() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + FeedbackRest feedbackRest = new FeedbackRest(); + + feedbackRest.setEmail("misha.boychuk@test.com"); + feedbackRest.setMessage("My feedback!"); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/feedbacks") + .content(mapper.writeValueAsBytes(feedbackRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + } + + @Test + public void sendFeedbackWithRecipientEmailNotConfiguredTest() throws Exception { + configurationService.setProperty("feedback.recipient", null); + ObjectMapper mapper = new ObjectMapper(); + FeedbackRest feedbackRest = new FeedbackRest(); + + feedbackRest.setEmail("misha.boychuk@test.com"); + feedbackRest.setMessage("My feedback!"); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/feedbacks") + .content(mapper.writeValueAsBytes(feedbackRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + } + + @Test + public void sendFeedbackBadRequestTest() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + FeedbackRest feedbackRest = new FeedbackRest(); + + feedbackRest.setMessage("My feedback!"); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/feedbacks") + .content(mapper.writeValueAsBytes(feedbackRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + +} \ No newline at end of file From 8a5df441045becb893ccd63103de3ebd8d80b707 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 3 Dec 2021 13:32:54 +0100 Subject: [PATCH 0505/1254] implemented canSendFeedback feature --- .../impl/CanSendFeedbackFeature.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSendFeedbackFeature.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSendFeedbackFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSendFeedbackFeature.java new file mode 100644 index 0000000000..4f88be5266 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSendFeedbackFeature.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.app.rest.authorization.impl; +import java.sql.SQLException; + +import org.apache.commons.lang.StringUtils; +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The send feedback feature. It can be used to verify if the parameter that contain + * recipient e-mail is configured. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +@Component +@AuthorizationFeatureDocumentation(name = CanSendFeedbackFeature.NAME, + description = "It can be used to verify if the parameter that contain recipient e-mail is configured.") +public class CanSendFeedbackFeature implements AuthorizationFeature { + + public static final String NAME = "canSendFeedback"; + + @Autowired + private ConfigurationService configurationService; + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + String recipientEmail = configurationService.getProperty("feedback.recipient"); + return StringUtils.isNotBlank(recipientEmail); + } + + @Override + public String[] getSupportedTypes() { + return new String[] { SiteRest.CATEGORY + "." + SiteRest.NAME }; + } + +} \ No newline at end of file From b4da463479eba7a186c8577d4622a7297218e98f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 3 Dec 2021 13:33:23 +0100 Subject: [PATCH 0506/1254] added tests for canSendFeedback feature --- .../CanSendFeedbackFeatureIT.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSendFeedbackFeatureIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSendFeedbackFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSendFeedbackFeatureIT.java new file mode 100644 index 0000000000..b4b9ab39c3 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSendFeedbackFeatureIT.java @@ -0,0 +1,110 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization; +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.authorization.impl.CanSendFeedbackFeature; +import org.dspace.app.rest.converter.SiteConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; +import org.dspace.services.ConfigurationService; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test for the canSendFeedback authorization feature. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class CanSendFeedbackFeatureIT extends AbstractControllerIntegrationTest { + + @Autowired + private SiteService siteService; + @Autowired + private SiteConverter siteConverter; + @Autowired + private ConfigurationService configurationService; + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + final String feature = "canSendFeedback"; + + private AuthorizationFeature canSendFeedbackFeature; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + canSendFeedbackFeature = authorizationFeatureService.find(CanSendFeedbackFeature.NAME); + } + + @Test + public void canSendFeedbackFeatureTest() throws Exception { + configurationService.setProperty("feedback.recipient", "myemail@test.com"); + + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEperson = getAuthToken(eperson.getEmail(), password); + + // define authorizations + Authorization authAdminSite = new Authorization(admin, canSendFeedbackFeature, siteRest); + Authorization authAnonymousSite = new Authorization(null, canSendFeedbackFeature, siteRest); + Authorization authEPersonSite = new Authorization(eperson, canSendFeedbackFeature, siteRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + authAdminSite.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(authAdminSite)))); + + getClient(tokenEperson).perform(get("/api/authz/authorizations/" + authEPersonSite.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(authEPersonSite)))); + + getClient().perform(get("/api/authz/authorizations/" + authAnonymousSite.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(authAnonymousSite)))); + } + + @Test + public void canSendFeedbackFeatureWithRecipientEmailNotConfiguredTest() throws Exception { + configurationService.setProperty("feedback.recipient", null); + + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEperson = getAuthToken(eperson.getEmail(), password); + + // define authorizations + Authorization authAdminSite = new Authorization(admin, canSendFeedbackFeature, siteRest); + Authorization authAnonymousSite = new Authorization(null, canSendFeedbackFeature, siteRest); + Authorization authEPersonSite = new Authorization(eperson, canSendFeedbackFeature, siteRest); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + authAdminSite.getID())) + .andExpect(status().isNotFound()); + + getClient(tokenEperson).perform(get("/api/authz/authorizations/" + authEPersonSite.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + authAnonymousSite.getID())) + .andExpect(status().isNotFound()); + } + +} \ No newline at end of file From 0f162d803ce4541f2edc6f0be008cdc707a8058e Mon Sep 17 00:00:00 2001 From: Joost Date: Fri, 3 Dec 2021 14:06:01 +0100 Subject: [PATCH 0507/1254] [task 82428] added a test to verify whether a timeout is being set --- .../export/service/OpenUrlServiceImpl.java | 9 +++--- .../service/OpenUrlServiceImplTest.java | 32 +++++++++++++++++-- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java index ca4434fbc2..b5badbb6d0 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java @@ -71,15 +71,14 @@ public class OpenUrlServiceImpl implements OpenUrlService { */ protected int getResponseCodeFromUrl(final String urlStr) throws IOException { HttpGet httpGet = new HttpGet(urlStr); - HttpClient httpClient = getHttpClient(); + RequestConfig requestConfig = getRequestConfigBuilder().setConnectTimeout(10*1000).build(); + HttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build(); HttpResponse httpResponse = httpClient.execute(httpGet); return httpResponse.getStatusLine().getStatusCode(); } - HttpClient getHttpClient(){ - //setting a timeout of 10 seconds so the connection pool doesn't exhaust when waiting a long time for a reply. - RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(10*1000).build(); - return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build(); + RequestConfig.Builder getRequestConfigBuilder(){ + return RequestConfig.custom(); } /** diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java index a67d0355d3..1625f0c180 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java @@ -11,6 +11,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -25,13 +26,26 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import com.amazonaws.http.client.HttpClientFactory; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicHttpResponse; +import org.apache.solr.client.solrj.impl.HttpClientBuilderFactory; import org.dspace.core.Context; import org.dspace.statistics.export.OpenURLTracker; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.Spy; +import org.mockito.internal.stubbing.answers.AnswersWithDelay; +import org.mockito.internal.stubbing.answers.Returns; import org.mockito.junit.MockitoJUnitRunner; /** @@ -61,8 +75,6 @@ public class OpenUrlServiceImplTest { openUrlService.processUrl(context, "test-url"); verify(openUrlService, times(0)).logfailed(context, "test-url"); - - } /** @@ -82,7 +94,6 @@ public class OpenUrlServiceImplTest { verify(openUrlService, times(1)).logfailed(context, "test-url"); - } /** @@ -131,4 +142,19 @@ public class OpenUrlServiceImplTest { assertThat(tracker1.getUrl(), is(failedUrl)); } + + @Test() + public void testTimeout() throws SQLException { + Context context = mock(Context.class); + String URL = "http://bla.com"; + + RequestConfig.Builder requestConfig = mock(RequestConfig.Builder.class); + doReturn(requestConfig).when(openUrlService).getRequestConfigBuilder(); + doReturn(requestConfig).when(requestConfig).setConnectTimeout(10*1000); + doReturn(RequestConfig.custom().build()).when(requestConfig).build(); + + openUrlService.processUrl(context, URL); + + Mockito.verify(requestConfig).setConnectTimeout(10*1000); + } } From 96b66fe7ff9a5ac839583e9bac25cdee49f4c6bb Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 3 Dec 2021 17:05:08 +0100 Subject: [PATCH 0508/1254] refactoring FeedbackRestRepository --- .../dspace/content/FeedbackServiceImpl.java | 45 +++++++++++++++++++ .../content/service/FeedbackService.java | 25 +++++++++++ .../repository/FeedbackRestRepository.java | 34 +++++--------- dspace/config/spring/api/core-services.xml | 1 + 4 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/FeedbackServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/content/service/FeedbackService.java diff --git a/dspace-api/src/main/java/org/dspace/content/FeedbackServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/FeedbackServiceImpl.java new file mode 100644 index 0000000000..5b182ab3a2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/FeedbackServiceImpl.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.content; +import java.io.IOException; +import java.util.Date; +import java.util.Objects; +import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.dspace.content.service.FeedbackService; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; + +public class FeedbackServiceImpl implements FeedbackService { + + @Override + public void sendEmail(Context context, HttpServletRequest request, String recipientEmail, String senderEmail, + String message, String page) throws IOException, MessagingException { + String session = request.getSession().getId(); + String agent = request.getHeader("User-Agent"); + String currentUserEmail = StringUtils.EMPTY; + + if (Objects.nonNull(context.getCurrentUser())) { + currentUserEmail = context.getCurrentUser().getEmail(); + } + Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), "feedback")); + email.addRecipient(recipientEmail); + email.addArgument(new Date()); // Date + email.addArgument(senderEmail); // Email + email.addArgument(currentUserEmail); // Logged in as + email.addArgument(page); // Referring page + email.addArgument(agent); // User agent + email.addArgument(session); // Session ID + email.addArgument(message); // The feedback itself + email.send(); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/service/FeedbackService.java b/dspace-api/src/main/java/org/dspace/content/service/FeedbackService.java new file mode 100644 index 0000000000..c643408679 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/FeedbackService.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.content.service; +import java.io.IOException; +import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.core.Context; + +/** + * Service interface class for the Feedback object. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public interface FeedbackService { + + public void sendEmail(Context context, HttpServletRequest request, String recipientEmail, String senderEmail, + String message, String page) throws IOException, MessagingException; + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java index c0e24e549b..c284a01a59 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/FeedbackRestRepository.java @@ -8,8 +8,6 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; -import java.util.Date; -import java.util.Objects; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; @@ -21,9 +19,8 @@ import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.FeedbackRest; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.service.FeedbackService; import org.dspace.core.Context; -import org.dspace.core.Email; -import org.dspace.core.I18nUtil; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -39,6 +36,8 @@ import org.springframework.stereotype.Component; @Component(FeedbackRest.CATEGORY + "." + FeedbackRest.NAME) public class FeedbackRestRepository extends DSpaceRestRepository { + @Autowired + private FeedbackService feedbackService; @Autowired private ConfigurationService configurationService; @@ -72,32 +71,15 @@ public class FeedbackRestRepository extends DSpaceRestRepository + From 3a600e930abc4ed276499753f5b069f79f81342f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 3 Dec 2021 17:05:27 +0100 Subject: [PATCH 0509/1254] refactoring tests --- .../app/rest/FeedbackRestRepositoryIT.java | 96 ++++++++++++++----- 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java index 496b52fb6f..1e67000974 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java @@ -6,13 +6,22 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNotNull; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; 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.status; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.model.FeedbackRest; +import org.dspace.app.rest.repository.FeedbackRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.FeedbackServiceImpl; +import org.dspace.content.service.FeedbackService; import org.dspace.services.ConfigurationService; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -26,6 +35,8 @@ public class FeedbackRestRepositoryIT extends AbstractControllerIntegrationTest @Autowired private ConfigurationService configurationService; + @Autowired + private FeedbackRestRepository feedbackRestRepository; @Test public void findAllTest() throws Exception { @@ -43,47 +54,80 @@ public class FeedbackRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void sendFeedbackTest() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - FeedbackRest feedbackRest = new FeedbackRest(); + configurationService.setProperty("feedback.recipient", "recipient.email@test.com"); + FeedbackService originFeedbackService = feedbackRestRepository.getFeedbackService(); + try { + FeedbackService feedbackServiceMock = mock (FeedbackServiceImpl.class); + feedbackRestRepository.setFeedbackService(feedbackServiceMock); - feedbackRest.setEmail("misha.boychuk@test.com"); - feedbackRest.setMessage("My feedback!"); + ObjectMapper mapper = new ObjectMapper(); + FeedbackRest feedbackRest = new FeedbackRest(); + + feedbackRest.setEmail("misha.boychuk@test.com"); + feedbackRest.setMessage("My feedback!"); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/feedbacks") + .content(mapper.writeValueAsBytes(feedbackRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + verify(feedbackServiceMock).sendEmail(isNotNull(), isNotNull(), eq("recipient.email@test.com"), + eq("misha.boychuk@test.com"), eq("My feedback!"), isNull()); + + verifyNoMoreInteractions(feedbackServiceMock); + } finally { + feedbackRestRepository.setFeedbackService(originFeedbackService); + } - String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/feedbacks") - .content(mapper.writeValueAsBytes(feedbackRest)) - .contentType(contentType)) - .andExpect(status().isCreated()); } @Test public void sendFeedbackWithRecipientEmailNotConfiguredTest() throws Exception { configurationService.setProperty("feedback.recipient", null); - ObjectMapper mapper = new ObjectMapper(); - FeedbackRest feedbackRest = new FeedbackRest(); + FeedbackService originFeedbackService = feedbackRestRepository.getFeedbackService(); + try { + FeedbackService feedbackServiceMock = mock (FeedbackServiceImpl.class); + feedbackRestRepository.setFeedbackService(feedbackServiceMock); + ObjectMapper mapper = new ObjectMapper(); + FeedbackRest feedbackRest = new FeedbackRest(); - feedbackRest.setEmail("misha.boychuk@test.com"); - feedbackRest.setMessage("My feedback!"); + feedbackRest.setEmail("misha.boychuk@test.com"); + feedbackRest.setMessage("My feedback!"); - String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/feedbacks") - .content(mapper.writeValueAsBytes(feedbackRest)) - .contentType(contentType)) - .andExpect(status().isNotFound()); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/feedbacks") + .content(mapper.writeValueAsBytes(feedbackRest)) + .contentType(contentType)) + .andExpect(status().isNotFound()); + + verifyNoMoreInteractions(feedbackServiceMock); + } finally { + feedbackRestRepository.setFeedbackService(originFeedbackService); + } } @Test public void sendFeedbackBadRequestTest() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - FeedbackRest feedbackRest = new FeedbackRest(); + FeedbackService originFeedbackService = feedbackRestRepository.getFeedbackService(); + try { + FeedbackService feedbackServiceMock = mock (FeedbackServiceImpl.class); + feedbackRestRepository.setFeedbackService(feedbackServiceMock); + ObjectMapper mapper = new ObjectMapper(); + FeedbackRest feedbackRest = new FeedbackRest(); - feedbackRest.setMessage("My feedback!"); + feedbackRest.setMessage("My feedback!"); - String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/feedbacks") - .content(mapper.writeValueAsBytes(feedbackRest)) - .contentType(contentType)) - .andExpect(status().isBadRequest()); + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/feedbacks") + .content(mapper.writeValueAsBytes(feedbackRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + verifyNoMoreInteractions(feedbackServiceMock); + } finally { + feedbackRestRepository.setFeedbackService(originFeedbackService); + } } } \ No newline at end of file From c74e769d24ad9dec223150174c5cf57e3fe3cd1b Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 3 Dec 2021 17:20:13 +0100 Subject: [PATCH 0510/1254] 85427: Blocking change password if the method is not password --- .../repository/EPersonRestRepository.java | 17 ++++--- .../app/rest/ShibbolethRestControllerIT.java | 50 +++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 403750e7f0..a1cd6dd25f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -293,17 +293,22 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository ops = new ArrayList(); + AddOperation addOperation = new AddOperation("/password", newPassword); + ops.add(addOperation); + String patchBody = getPatchContent(ops); + + // login through shibboleth + String token = getClient().perform(get("/api/authn/shibboleth").requestAttr("SHIB-MAIL", eperson.getEmail())) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000")) + .andReturn().getResponse().getHeader("Authorization"); + + + getClient(token).perform(get("/api/authn/status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authenticated", is(true))) + .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + + // updates password + getClient(token).perform(patch("/api/eperson/epersons/" + ePerson.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + } + } From 160f0221e75a409c0b520128ebcfcb9860e82239 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 3 Dec 2021 16:07:57 -0500 Subject: [PATCH 0511/1254] Fixed default, configured Handle URL stem to use https: #8018 Also tidied logging and commentary. --- .../org/dspace/handle/HandleServiceImpl.java | 55 +++++++++---------- .../dspace/handle/service/HandleService.java | 12 ++-- .../java/org/dspace/harvest/OAIHarvester.java | 2 +- dspace/config/dspace.cfg | 13 +++-- dspace/config/local.cfg.EXAMPLE | 10 ++-- 5 files changed, 43 insertions(+), 49 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 645b1fdbc4..c417aa4794 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java @@ -15,6 +15,7 @@ import java.util.regex.Pattern; import org.apache.commons.collections4.CollectionUtils; 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.service.SiteService; @@ -25,10 +26,9 @@ import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; - /** - * Interface to the CNRI Handle - * System . + * Interface to the CNRI Handle + * System. * *

    * Currently, this class simply maps handles to local facilities; handles which @@ -37,13 +37,12 @@ import org.springframework.beans.factory.annotation.Autowired; *

    * * @author Peter Breton - * @version $Revision$ */ public class HandleServiceImpl implements HandleService { /** - * log4j category + * log category */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(HandleServiceImpl.class); + private static final Logger log = LogManager.getLogger(); /** * Prefix registered to no one @@ -84,9 +83,7 @@ public class HandleServiceImpl implements HandleService { String url = configurationService.getProperty("dspace.ui.url") + "/handle/" + handle; - if (log.isDebugEnabled()) { - log.debug("Resolved " + handle + " to " + url); - } + log.debug("Resolved {} to {}", handle, url); return url; } @@ -96,7 +93,7 @@ public class HandleServiceImpl implements HandleService { throws SQLException { String dspaceUrl = configurationService.getProperty("dspace.ui.url") + "/handle/"; - String handleResolver = configurationService.getProperty("handle.canonical.prefix"); + String handleResolver = getCanonicalPrefix(); String handle = null; @@ -126,10 +123,8 @@ public class HandleServiceImpl implements HandleService { // Let the admin define a new prefix, if not then we'll use the // CNRI default. This allows the admin to use "hdl:" if they want to or // use a locally branded prefix handle.myuni.edu. - String handlePrefix = configurationService.getProperty("handle.canonical.prefix"); - if (StringUtils.isBlank(handlePrefix)) { - handlePrefix = "http://hdl.handle.net/"; - } + String handlePrefix = configurationService.getProperty("handle.canonical.prefix", + "https://hdl.handle.net/"); return handlePrefix; } @@ -151,10 +146,10 @@ public class HandleServiceImpl implements HandleService { handle.setResourceTypeId(dso.getType()); handleDAO.save(context, handle); - if (log.isDebugEnabled()) { - log.debug("Created new handle for " - + Constants.typeText[dso.getType()] + " (ID=" + dso.getID() + ") " + handleId); - } + log.debug("Created new handle for {} (ID={}) {}", + () -> Constants.typeText[dso.getType()], + () -> dso.getID(), + () -> handleId); return handleId; } @@ -205,10 +200,10 @@ public class HandleServiceImpl implements HandleService { dso.addHandle(handle); handleDAO.save(context, handle); - if (log.isDebugEnabled()) { - log.debug("Created new handle for " - + Constants.typeText[dso.getType()] + " (ID=" + dso.getID() + ") " + suppliedHandle); - } + log.debug("Created new handle for {} (ID={}) {}", + () -> Constants.typeText[dso.getType()], + () -> dso.getID(), + () -> suppliedHandle); return suppliedHandle; } @@ -230,15 +225,15 @@ public class HandleServiceImpl implements HandleService { handleDAO.save(context, handle); - if (log.isDebugEnabled()) { - log.debug("Unbound Handle " + handle.getHandle() + " from object " + Constants.typeText[dso - .getType()] + " id=" + dso.getID()); - } + log.debug("Unbound Handle {} from object {} id={}", + () -> handle.getHandle(), + () -> Constants.typeText[dso.getType()], + () -> dso.getID()); } } else { log.trace( - "Cannot find Handle entry to unbind for object " + Constants.typeText[dso.getType()] + " id=" + dso - .getID() + ". Handle could have been unbinded before."); + "Cannot find Handle entry to unbind for object {} id={}. Handle could have been unbound before.", + Constants.typeText[dso.getType()], dso.getID()); } } @@ -284,7 +279,7 @@ public class HandleServiceImpl implements HandleService { public List getHandlesForPrefix(Context context, String prefix) throws SQLException { List handles = handleDAO.findByPrefix(context, prefix); - List handleStrings = new ArrayList(handles.size()); + List handleStrings = new ArrayList<>(handles.size()); for (Handle handle : handles) { handleStrings.add(handle.getHandle()); } @@ -296,7 +291,7 @@ public class HandleServiceImpl implements HandleService { String prefix = configurationService.getProperty("handle.prefix"); if (StringUtils.isBlank(prefix)) { prefix = EXAMPLE_PREFIX; // XXX no good way to exit cleanly - log.error("handle.prefix is not configured; using " + prefix); + log.error("handle.prefix is not configured; using {}", prefix); } return prefix; } diff --git a/dspace-api/src/main/java/org/dspace/handle/service/HandleService.java b/dspace-api/src/main/java/org/dspace/handle/service/HandleService.java index 62dec25587..c7de7411ef 100644 --- a/dspace-api/src/main/java/org/dspace/handle/service/HandleService.java +++ b/dspace-api/src/main/java/org/dspace/handle/service/HandleService.java @@ -14,8 +14,8 @@ import org.dspace.content.DSpaceObject; import org.dspace.core.Context; /** - * Interface to the CNRI Handle - * System . + * Interface to the CNRI Handle + * System. * *

    * Currently, this class simply maps handles to local facilities; handles which @@ -24,7 +24,6 @@ import org.dspace.core.Context; *

    * * @author Peter Breton - * @version $Revision$ */ public interface HandleService { @@ -42,7 +41,6 @@ public interface HandleService { public String resolveToURL(Context context, String handle) throws SQLException; - /** * Try to detect a handle in a URL. * @@ -56,18 +54,18 @@ public interface HandleService { throws SQLException; /** - * Provides handle canonical prefix using http://hdl.handle.net if not + * Provides handle canonical prefix using https://hdl.handle.net if not * overridden by the configuration property handle.canonical.prefix. * * No attempt is made to verify that handle is in fact valid. * - * @param handle The handle + * * @return The canonical form */ public String getCanonicalPrefix(); /** - * Transforms handle into a URI using http://hdl.handle.net if not + * Transforms handle into a URI using https://hdl.handle.net if not * overridden by the configuration property handle.canonical.prefix. * * No attempt is made to verify that handle is in fact valid. 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 61385095e8..71e00d73d7 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java +++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java @@ -701,7 +701,7 @@ public class OAIHarvester { if (values.size() > 0 && acceptedHandleServers != null) { for (MetadataValue value : values) { // 0 1 2 3 4 - // http://hdl.handle.net/1234/12 + // https://hdl.handle.net/1234/12 String[] urlPieces = value.getValue().split("/"); if (urlPieces.length != 5) { continue; diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 1bd86b16f2..1bcfdd0c19 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -258,13 +258,13 @@ identifier.doi.namespaceseparator = dspace/ # after it is generated during the submission process. handle.canonical.prefix = ${dspace.ui.url}/handle/ -# If you register with CNRI's handle service at http://www.handle.net/, -# these links can be generated as permalinks using http://hdl.handle.net/ +# If you register with CNRI's handle service at https://www.handle.net/, +# these links can be generated as permalinks using https://hdl.handle.net/ # as canonical prefix. Please make sure to change handle.canonical.prefix # after registering with handle.net by uncommenting one of the following -# lines, depending if you prefer to use http or https: -# handle.canonical.prefix = http://hdl.handle.net/ +# lines, depending if you prefer to use https or http: # handle.canonical.prefix = https://hdl.handle.net/ +# handle.canonical.prefix = http://hdl.handle.net/ # # Note that this will not alter dc.identifier.uri metadata for existing # items (only for subsequent submissions). @@ -1163,8 +1163,9 @@ webui.feed.cache.age = 48 # use one or more (comma-separated) values from list: # rss_0.90, rss_0.91, rss_0.92, rss_0.93, rss_0.94, rss_1.0, rss_2.0 webui.feed.formats = rss_1.0,rss_2.0,atom_1.0 -# URLs returned by the feed will point at the global handle server (e.g. http://hdl.handle.net/123456789/1) -# Set to true to use local server URLs (i.e. http://myserver.myorg/handle/123456789/1) +# URLs returned by the feed will point at the global handle server +# (e.g. https://hdl.handle.net/123456789/1). Set to true to use local server +# URLs (i.e. https://myserver.myorg/handle/123456789/1) webui.feed.localresolve = false # Customize each single-value field displayed in the diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 7bd79257d9..f8a931cd25 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -144,19 +144,19 @@ db.schema = public # Items in DSpace receive a unique URL, stored in dc.identifier.uri # after it is generated during the submission process. # -# If you register with CNRI's handle service at http://www.handle.net/, -# these links can be generated as permalinks using http://hdl.handle.net/ +# If you register with CNRI's handle service at https://www.handle.net/, +# these links can be generated as permalinks using https://hdl.handle.net/ # as canonical prefix. Please make sure to change handle.canonical.prefix # after registering with handle.net by uncommenting one of the following -# lines, depending if you prefer to use http or https: -# handle.canonical.prefix = http://hdl.handle.net/ +# lines, depending if you prefer to use https or http: # handle.canonical.prefix = https://hdl.handle.net/ +# handle.canonical.prefix = http://hdl.handle.net/ # # Note that this will not alter dc.identifier.uri metadata for existing # items (only for subsequent submissions). # CNRI Handle prefix -# (Defaults to a dummy/fake prefix of 123456789) +# (Defaults to the reserved dummy/fake prefix of 123456789) #handle.prefix = 123456789 ####################### From 99ed35ceedd74947a6af77906f438a743960d402 Mon Sep 17 00:00:00 2001 From: Joost Date: Mon, 6 Dec 2021 11:51:07 +0100 Subject: [PATCH 0512/1254] added javadoc and made getRequestConfig protected --- .../statistics/export/service/OpenUrlServiceImpl.java | 10 ++++------ .../export/service/OpenUrlServiceImplTest.java | 6 +++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java index b5badbb6d0..e3842602db 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java @@ -9,12 +9,9 @@ package org.dspace.statistics.export.service; import java.io.IOException; import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; import java.sql.SQLException; import java.util.Date; import java.util.List; -import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpResponse; @@ -24,7 +21,6 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.tools.ant.taskdefs.condition.Http; import org.dspace.core.Context; import org.dspace.statistics.export.OpenURLTracker; import org.springframework.beans.factory.annotation.Autowired; @@ -64,7 +60,9 @@ public class OpenUrlServiceImpl implements OpenUrlService { } /** - * Returns the response code from accessing the url + * Returns the response code from accessing the url. Returns a http status 408 when the external service doesn't + * reply in 10 seconds + * * @param urlStr * @return response code from the url * @throws IOException @@ -77,7 +75,7 @@ public class OpenUrlServiceImpl implements OpenUrlService { return httpResponse.getStatusLine().getStatusCode(); } - RequestConfig.Builder getRequestConfigBuilder(){ + protected RequestConfig.Builder getRequestConfigBuilder(){ return RequestConfig.custom(); } diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java index 1625f0c180..7fe7fdc114 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java @@ -143,7 +143,11 @@ public class OpenUrlServiceImplTest { } - @Test() + /** + * Tests whether the timeout gets set to 10 seconds when processing a url + * @throws SQLException + */ + @Test public void testTimeout() throws SQLException { Context context = mock(Context.class); String URL = "http://bla.com"; From 9c988c36533be68d51e6294c9f79bb69c540846d Mon Sep 17 00:00:00 2001 From: Pongtawat C Date: Mon, 6 Dec 2021 21:29:48 +0700 Subject: [PATCH 0513/1254] Fix sample log rollover config --- dspace/config/log4j2.xml | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/dspace/config/log4j2.xml b/dspace/config/log4j2.xml index eeab2bc9c6..a281c1cb12 100644 --- a/dspace/config/log4j2.xml +++ b/dspace/config/log4j2.xml @@ -32,13 +32,15 @@ pattern='%d %-5p $$$${ctx:correlationID:-unknown} $$$${ctx:requestID:-unknown} %c @ %m%n'/> yyyy-MM-dd - + @@ -50,13 +52,15 @@ pattern='%m%n'/> yyyy-MM-dd - + From 22e951238474a74d12026dff6ceeb9b1e944cada Mon Sep 17 00:00:00 2001 From: Joost Date: Mon, 6 Dec 2021 17:05:37 +0100 Subject: [PATCH 0514/1254] checkstyle fixes --- .../export/service/OpenUrlServiceImpl.java | 6 +++--- .../export/service/OpenUrlServiceImplTest.java | 16 ++-------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java index e3842602db..7dc5276951 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/export/service/OpenUrlServiceImpl.java @@ -69,13 +69,13 @@ public class OpenUrlServiceImpl implements OpenUrlService { */ protected int getResponseCodeFromUrl(final String urlStr) throws IOException { HttpGet httpGet = new HttpGet(urlStr); - RequestConfig requestConfig = getRequestConfigBuilder().setConnectTimeout(10*1000).build(); - HttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build(); + RequestConfig requestConfig = getRequestConfigBuilder().setConnectTimeout(10 * 1000).build(); + HttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build(); HttpResponse httpResponse = httpClient.execute(httpGet); return httpResponse.getStatusLine().getStatusCode(); } - protected RequestConfig.Builder getRequestConfigBuilder(){ + protected RequestConfig.Builder getRequestConfigBuilder() { return RequestConfig.custom(); } diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java index 7fe7fdc114..2d414cfcc8 100644 --- a/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/statistics/export/service/OpenUrlServiceImplTest.java @@ -11,7 +11,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -26,26 +25,15 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import com.amazonaws.http.client.HttpClientFactory; -import org.apache.http.HttpVersion; -import org.apache.http.ProtocolVersion; -import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.message.BasicHttpResponse; -import org.apache.solr.client.solrj.impl.HttpClientBuilderFactory; import org.dspace.core.Context; import org.dspace.statistics.export.OpenURLTracker; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.Spy; -import org.mockito.internal.stubbing.answers.AnswersWithDelay; -import org.mockito.internal.stubbing.answers.Returns; import org.mockito.junit.MockitoJUnitRunner; /** @@ -154,11 +142,11 @@ public class OpenUrlServiceImplTest { RequestConfig.Builder requestConfig = mock(RequestConfig.Builder.class); doReturn(requestConfig).when(openUrlService).getRequestConfigBuilder(); - doReturn(requestConfig).when(requestConfig).setConnectTimeout(10*1000); + doReturn(requestConfig).when(requestConfig).setConnectTimeout(10 * 1000); doReturn(RequestConfig.custom().build()).when(requestConfig).build(); openUrlService.processUrl(context, URL); - Mockito.verify(requestConfig).setConnectTimeout(10*1000); + Mockito.verify(requestConfig).setConnectTimeout(10 * 1000); } } From 0af0361660584d6897f8454e89ab6ab793a40c71 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 6 Dec 2021 15:06:57 -0600 Subject: [PATCH 0515/1254] Minor updates to build action --- .github/workflows/build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67ba6213e3..2daa0e5956 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest env: # Give Maven 1GB of memory to work with - # Suppress all Maven "downloading" messages in Travis logs (see https://stackoverflow.com/a/35653426) + # Suppress all Maven "downloading" messages in logs (see https://stackoverflow.com/a/35653426) # This also slightly speeds builds, as there is less logging MAVEN_OPTS: "-Xmx1024M -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" strategy: @@ -38,13 +38,14 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v1 + uses: actions/checkout@v2 # https://github.com/actions/setup-java - name: Install JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 11 + distribution: 'temurin' # https://github.com/actions/cache - name: Cache Maven dependencies From 2a040a35ada53a1fb93b920a959069a89933a96e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 6 Dec 2021 15:18:18 -0600 Subject: [PATCH 0516/1254] Create GitHub action to build all DSpace Docker images --- .github/workflows/docker.yml | 147 +++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..50f233de7e --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,147 @@ +# DSpace Docker image build for hub.docker.com +name: Docker images + +# Run this Build for all pushes / PRs to current branch +on: [push, pull_request] + +jobs: + docker: + runs-on: ubuntu-latest + env: + # Give Maven 1GB of memory to work with + # Suppress all Maven "downloading" messages in logs (see https://stackoverflow.com/a/35653426) + # This also slightly speeds builds, as there is less logging + MAVEN_OPTS: "-Xmx1024M -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" + # Define tags to use for Docker images based on Git tags/branches + # For a new branch commit, tag Docker image with that branch name + # Except for 'main' branch, where we use the literal 'dspace-7_x' tag (see below). + # For a new tag, tag Docker image with that same tag + IMAGE_TAGS: | + type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }} + type=raw,value=dspace-7_x,enable=${{ github.ref == 'refs/heads/main' }} + type=ref,event=tag + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v2 + + # https://github.com/actions/setup-java + - name: Install JDK 11 + uses: actions/setup-java@v2 + with: + java-version: 11 + distribution: 'temurin' + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v1 + + # https://github.com/docker/login-action + - 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 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + #################################################### + # Build/Push the 'dspace/dspace-dependencies' image + #################################################### + # https://github.com/docker/metadata-action + # 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 + with: + images: dspace/dspace-dependencies + tags: ${{ env.IMAGE_TAGS }} + + # https://github.com/docker/build-push-action + - name: Build and push 'dspace-dependencies' image + id: docker_build_deps + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile.dependencies + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build_deps.outputs.tags }} + labels: ${{ steps.meta_build_deps.outputs.labels }} + + ####################################### + # Build/Push the 'dspace/dspace' image + ####################################### + # 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 + with: + images: dspace/dspace + tags: ${{ env.IMAGE_TAGS }} + + - name: Build and push 'dspace' image + id: docker_build + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build.outputs.tags }} + labels: ${{ steps.meta_build.outputs.labels }} + + ##################################################### + # Build/Push the 'dspace/dspace' image ('-test' tag) + ##################################################### + # 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 + with: + images: dspace/dspace + # ONLY add 'dspace-7_x-test' version tag if this is the 'main' branch. + # (No other tags are added for this image, as it's only used for testing/development) + tags: | + type=raw,value=dspace-7_x-test,enable=${{ github.ref == 'refs/heads/main' }} + + - name: Build and push 'dspace-test' image + id: docker_build_test + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile.test + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build_test.outputs.tags }} + labels: ${{ steps.meta_build_test.outputs.labels }} + + ########################################### + # Build/Push the 'dspace/dspace-cli' image + ########################################### + # 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 + with: + images: dspace/dspace-cli + tags: ${{ env.IMAGE_TAGS }} + + - name: Build and push 'dspace-cli' image + id: docker_build_cli + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile.cli + # For pull requests, we run the Docker build (to ensure no PR changes break the build), + # but we ONLY do an image push to DockerHub if it's NOT a PR + push: ${{ github.event_name != 'pull_request' }} + # Use tags / labels provided by 'docker/metadata-action' above + tags: ${{ steps.meta_build_cli.outputs.tags }} + labels: ${{ steps.meta_build_cli.outputs.labels }} \ No newline at end of file From 7db58260703fef9e5ad041827c842d1159753d36 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 Dec 2021 16:25:27 -0600 Subject: [PATCH 0517/1254] Minor updates to Docker README --- dspace/src/main/docker/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace/src/main/docker/README.md b/dspace/src/main/docker/README.md index 260e55599d..d0bd92e949 100644 --- a/dspace/src/main/docker/README.md +++ b/dspace/src/main/docker/README.md @@ -11,11 +11,11 @@ This Dockerfile is used to pre-cache Maven dependency downloads that will be use docker build -t dspace/dspace-dependencies:dspace-7_x -f Dockerfile.dependencies . ``` -**This image is built manually.** It should be rebuilt each year or after each major release in order to refresh the cache of jars. +This image is built *automatically* after each commit is made to the `main` branch. A corresponding image exists for DSpace 4-6. -Admins to our DockerHub repo can publish with the following command. +Admins to our DockerHub repo can manually publish with the following command. ``` docker push dspace/dspace-dependencies:dspace-7_x ``` @@ -35,7 +35,7 @@ This image is built *automatically* after each commit is made to the `main` bran A corresponding image exists for DSpace 4-6. -Admins to our DockerHub repo can publish with the following command. +Admins to our DockerHub repo can manually publish with the following command. ``` docker push dspace/dspace:dspace-7_x-test ``` From 588829c6de847471e422ec9800482616d8d34cfc Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 Dec 2021 16:34:20 -0600 Subject: [PATCH 0518/1254] Update to latest version of codecov-action --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2daa0e5956..6a483efb6b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,4 +75,4 @@ jobs: # https://github.com/codecov/codecov-action - name: Upload coverage to Codecov.io - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 From 6f92ff4d34560c59de29d9dc3a7c72ea3928de98 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 7 Dec 2021 16:38:03 -0600 Subject: [PATCH 0519/1254] Remove unnecessary Java steps --- .github/workflows/docker.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 50f233de7e..a6a8ae9099 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,10 +8,6 @@ jobs: docker: runs-on: ubuntu-latest env: - # Give Maven 1GB of memory to work with - # Suppress all Maven "downloading" messages in logs (see https://stackoverflow.com/a/35653426) - # This also slightly speeds builds, as there is less logging - MAVEN_OPTS: "-Xmx1024M -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" # Define tags to use for Docker images based on Git tags/branches # For a new branch commit, tag Docker image with that branch name # Except for 'main' branch, where we use the literal 'dspace-7_x' tag (see below). @@ -26,13 +22,6 @@ jobs: - name: Checkout codebase uses: actions/checkout@v2 - # https://github.com/actions/setup-java - - name: Install JDK 11 - uses: actions/setup-java@v2 - with: - java-version: 11 - distribution: 'temurin' - - name: Setup Docker Buildx uses: docker/setup-buildx-action@v1 From 03b347016645ed555904809e45495a5765ce033d Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 8 Dec 2021 13:14:51 +0100 Subject: [PATCH 0520/1254] 85542: Turn filter-media into a scripts and processes script --- ...terCLITool.java => MediaFilterScript.java} | 195 ++++++------------ .../MediaFilterScriptConfiguration.java | 91 ++++++++ dspace/config/launcher.xml | 7 - dspace/config/spring/api/scripts.xml | 5 + 4 files changed, 160 insertions(+), 138 deletions(-) rename dspace-api/src/main/java/org/dspace/app/mediafilter/{MediaFilterCLITool.java => MediaFilterScript.java} (54%) create mode 100644 dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterCLITool.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java similarity index 54% rename from dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterCLITool.java rename to dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java index 067419df22..36d3a1fe87 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterCLITool.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java @@ -14,13 +14,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.MissingArgumentException; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.ArrayUtils; import org.dspace.app.mediafilter.factory.MediaFilterServiceFactory; import org.dspace.app.mediafilter.service.MediaFilterService; @@ -33,7 +27,9 @@ import org.dspace.core.Context; import org.dspace.core.SelfNamedPlugin; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.scripts.DSpaceRunnable; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; /** * MediaFilterManager is the class that invokes the media/format filters over the @@ -44,7 +40,7 @@ import org.dspace.services.factory.DSpaceServicesFactory; * scope to a community, collection or item; and -m [max] limits processing to a * maximum number of items. */ -public class MediaFilterCLITool { +public class MediaFilterScript extends DSpaceRunnable { //key (in dspace.cfg) which lists all enabled filters by name private static final String MEDIA_FILTER_PLUGINS_KEY = "filter.plugins"; @@ -55,126 +51,76 @@ public class MediaFilterCLITool { //suffix (in dspace.cfg) for input formats supported by each filter private static final String INPUT_FORMATS_SUFFIX = "inputFormats"; - /** - * Default constructor - */ - private MediaFilterCLITool() { } + private boolean help; + private boolean isVerbose = false; + private boolean isQuiet = false; + private boolean isForce = false; // default to not forced + private String identifier = null; // object scope limiter + private int max2Process = Integer.MAX_VALUE; + private String[] filterNames; + private String[] skipIds = null; + private Map> filterFormats = new HashMap<>(); + + public MediaFilterScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager() + .getServiceByName("filter-media", MediaFilterScriptConfiguration.class); + } + + public void setup() throws ParseException { - public static void main(String[] argv) throws Exception { // set headless for non-gui workstations System.setProperty("java.awt.headless", "true"); - // create an options object and populate it - CommandLineParser parser = new DefaultParser(); - int status = 0; + help = commandLine.hasOption('h'); - Options options = new Options(); - - options.addOption("v", "verbose", false, - "print all extracted text and other details to STDOUT"); - options.addOption("q", "quiet", false, - "do not print anything except in the event of errors."); - options.addOption("f", "force", false, - "force all bitstreams to be processed"); - options.addOption("i", "identifier", true, - "ONLY process bitstreams belonging to identifier"); - options.addOption("m", "maximum", true, - "process no more than maximum items"); - options.addOption("h", "help", false, "help"); - - //create a "plugin" option (to specify specific MediaFilter plugins to run) - Option pluginOption = Option.builder("p") - .longOpt("plugins") - .hasArg() - .hasArgs() - .valueSeparator(',') - .desc( - "ONLY run the specified Media Filter plugin(s)\n" + - "listed from '" + MEDIA_FILTER_PLUGINS_KEY + "' in dspace.cfg.\n" + - "Separate multiple with a comma (,)\n" + - "(e.g. MediaFilterManager -p \n\"Word Text Extractor\",\"PDF Text Extractor\")") - .build(); - options.addOption(pluginOption); - - //create a "skip" option (to specify communities/collections/items to skip) - Option skipOption = Option.builder("s") - .longOpt("skip") - .hasArg() - .hasArgs() - .valueSeparator(',') - .desc( - "SKIP the bitstreams belonging to identifier\n" + - "Separate multiple identifiers with a comma (,)\n" + - "(e.g. MediaFilterManager -s \n 123456789/34,123456789/323)") - .build(); - options.addOption(skipOption); - - boolean isVerbose = false; - boolean isQuiet = false; - boolean isForce = false; // default to not forced - String identifier = null; // object scope limiter - int max2Process = Integer.MAX_VALUE; - Map> filterFormats = new HashMap<>(); - - CommandLine line = null; - try { - line = parser.parse(options, argv); - } catch (MissingArgumentException e) { - System.out.println("ERROR: " + e.getMessage()); - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MediaFilterManager\n", options); - System.exit(1); - } - - if (line.hasOption('h')) { - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MediaFilterManager\n", options); - - System.exit(0); - } - - if (line.hasOption('v')) { + if (commandLine.hasOption('v')) { isVerbose = true; } - isQuiet = line.hasOption('q'); + isQuiet = commandLine.hasOption('q'); - if (line.hasOption('f')) { + if (commandLine.hasOption('f')) { isForce = true; } - if (line.hasOption('i')) { - identifier = line.getOptionValue('i'); + if (commandLine.hasOption('i')) { + identifier = commandLine.getOptionValue('i'); } - if (line.hasOption('m')) { - max2Process = Integer.parseInt(line.getOptionValue('m')); + if (commandLine.hasOption('m')) { + max2Process = Integer.parseInt(commandLine.getOptionValue('m')); if (max2Process <= 1) { - System.out.println("Invalid maximum value '" + - line.getOptionValue('m') + "' - ignoring"); + handler.logWarning("Invalid maximum value '" + + commandLine.getOptionValue('m') + "' - ignoring"); max2Process = Integer.MAX_VALUE; } } - String filterNames[] = null; - if (line.hasOption('p')) { + if (commandLine.hasOption('p')) { //specified which media filter plugins we are using - filterNames = line.getOptionValues('p'); - - if (filterNames == null || filterNames.length == 0) { //display error, since no plugins specified - System.err.println("\nERROR: -p (-plugin) option requires at least one plugin to be specified.\n" + - "(e.g. MediaFilterManager -p \"Word Text Extractor\",\"PDF Text Extractor\")\n"); - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MediaFilterManager\n", options); - System.exit(1); - } + filterNames = commandLine.getOptionValues('p'); } else { //retrieve list of all enabled media filter plugins! filterNames = DSpaceServicesFactory.getInstance().getConfigurationService() .getArrayProperty(MEDIA_FILTER_PLUGINS_KEY); } + //save to a global skip list + if (commandLine.hasOption('s')) { + //specified which identifiers to skip when processing + skipIds = commandLine.getOptionValues('s'); + } + + + } + + public void internalRun() throws Exception { + if (help) { + printHelp(); + return; + } + MediaFilterService mediaFilterService = MediaFilterServiceFactory.getInstance().getMediaFilterService(); mediaFilterService.setForce(isForce); mediaFilterService.setQuiet(isQuiet); @@ -184,16 +130,17 @@ public class MediaFilterCLITool { //initialize an array of our enabled filters List filterList = new ArrayList<>(); + //set up each filter for (int i = 0; i < filterNames.length; i++) { //get filter of this name & add to list of filters FormatFilter filter = (FormatFilter) CoreServiceFactory.getInstance().getPluginService() .getNamedPlugin(FormatFilter.class, filterNames[i]); if (filter == null) { - System.err.println( - "\nERROR: Unknown MediaFilter specified (either from command-line or in dspace.cfg): '" + - filterNames[i] + "'"); - System.exit(1); + handler.handleException("ERROR: Unknown MediaFilter specified (either from command-line or in " + + "dspace.cfg): '" + filterNames[i] + "'"); + handler.logError("ERROR: Unknown MediaFilter specified (either from command-line or in " + + "dspace.cfg): '" + filterNames[i] + "'"); } else { filterList.add(filter); @@ -218,10 +165,10 @@ public class MediaFilterCLITool { //For other MediaFilters, format of key is: // filter..inputFormats String[] formats = - DSpaceServicesFactory.getInstance().getConfigurationService().getArrayProperty( - FILTER_PREFIX + "." + filterClassName + - (pluginName != null ? "." + pluginName : "") + - "." + INPUT_FORMATS_SUFFIX); + DSpaceServicesFactory.getInstance().getConfigurationService().getArrayProperty( + FILTER_PREFIX + "." + filterClassName + + (pluginName != null ? "." + pluginName : "") + + "." + INPUT_FORMATS_SUFFIX); //add to internal map of filters to supported formats if (ArrayUtils.isNotEmpty(formats)) { @@ -230,8 +177,8 @@ public class MediaFilterCLITool { //For other MediaFilters, map key is just: // filterFormats.put(filterClassName + - (pluginName != null ? MediaFilterService.FILTER_PLUGIN_SEPARATOR + - pluginName : ""), + (pluginName != null ? MediaFilterService.FILTER_PLUGIN_SEPARATOR + + pluginName : ""), Arrays.asList(formats)); } } //end if filter!=null @@ -239,11 +186,11 @@ public class MediaFilterCLITool { //If verbose, print out loaded mediafilter info if (isVerbose) { - System.out.println("The following MediaFilters are enabled: "); + handler.logInfo("The following MediaFilters are enabled: "); Iterator i = filterFormats.keySet().iterator(); while (i.hasNext()) { String filterName = i.next(); - System.out.println("Full Filter Name: " + filterName); + handler.logInfo("Full Filter Name: " + filterName); String pluginName = null; if (filterName.contains(MediaFilterService.FILTER_PLUGIN_SEPARATOR)) { String[] fields = filterName.split(MediaFilterService.FILTER_PLUGIN_SEPARATOR); @@ -251,8 +198,7 @@ public class MediaFilterCLITool { pluginName = fields[1]; } - System.out.println(filterName + - (pluginName != null ? " (Plugin: " + pluginName + ")" : "")); + handler.logInfo(filterName + (pluginName != null ? " (Plugin: " + pluginName + ")" : "")); } } @@ -262,20 +208,8 @@ public class MediaFilterCLITool { //Retrieve list of identifiers to skip (if any) - String skipIds[] = null; - if (line.hasOption('s')) { - //specified which identifiers to skip when processing - skipIds = line.getOptionValues('s'); - - if (skipIds == null || skipIds.length == 0) { //display error, since no identifiers specified to skip - System.err.println("\nERROR: -s (-skip) option requires at least one identifier to SKIP.\n" + - "Make sure to separate multiple identifiers with a comma!\n" + - "(e.g. MediaFilterManager -s 123456789/34,123456789/323)\n"); - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MediaFilterManager\n", options); - System.exit(0); - } + if (skipIds != null && skipIds.length > 0) { //save to a global skip list mediaFilterService.setSkipList(Arrays.asList(skipIds)); } @@ -296,7 +230,7 @@ public class MediaFilterCLITool { DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(c, identifier); if (dso == null) { throw new IllegalArgumentException("Cannot resolve " - + identifier + " to a DSpace object"); + + identifier + " to a DSpace object"); } switch (dso.getType()) { @@ -317,12 +251,11 @@ public class MediaFilterCLITool { c.complete(); c = null; } catch (Exception e) { - status = 1; + handler.handleException(e); } finally { if (c != null) { c.abort(); } } - System.exit(status); } } diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java new file mode 100644 index 0000000000..49ee23b924 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.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.app.mediafilter; + +import java.sql.SQLException; + +import org.apache.commons.cli.Option; +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; + +public class MediaFilterScriptConfiguration extends ScriptConfiguration { + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + private static final String MEDIA_FILTER_PLUGINS_KEY = "filter.plugins"; + + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + + @Override + public boolean isAllowedToExecute(final 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() { + Options options = new Options(); + options.addOption("v", "verbose", false, "print all extracted text and other details to STDOUT"); + options.getOption("v").setType(boolean.class); + options.addOption("q", "quiet", false, "do not print anything except in the event of errors."); + options.getOption("q").setType(boolean.class); + options.addOption("f", "force", false, "force all bitstreams to be processed"); + options.getOption("f").setType(boolean.class); + options.addOption("i", "identifier", true, "ONLY process bitstreams belonging to identifier"); + options.addOption("m", "maximum", true, "process no more than maximum items"); + options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); + + Option pluginOption = Option.builder("p") + .longOpt("plugins") + .hasArg() + .hasArgs() + .valueSeparator(',') + .desc( + "ONLY run the specified Media Filter plugin(s)\n" + + "listed from '" + MEDIA_FILTER_PLUGINS_KEY + "' in dspace.cfg.\n" + + "Separate multiple with a comma (,)\n" + + "(e.g. MediaFilterManager -p \n\"Word Text Extractor\",\"PDF Text" + + " Extractor\")") + .build(); + options.addOption(pluginOption); + + Option skipOption = Option.builder("s") + .longOpt("skip") + .hasArg() + .hasArgs() + .valueSeparator(',') + .desc( + "SKIP the bitstreams belonging to identifier\n" + + "Separate multiple identifiers with a comma (,)\n" + + "(e.g. MediaFilterManager -s \n 123456789/34,123456789/323)") + .build(); + options.addOption(skipOption); + + return options; + } +} diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index d92e0e5880..54e2678726 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -96,13 +96,6 @@ org.dspace.app.itemexport.ItemExportCLITool - - filter-media - Perform the media filtering to extract full text from documents and to create thumbnails - - org.dspace.app.mediafilter.MediaFilterCLITool - - generate-sitemaps Generate search engine and html sitemaps diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 52467dabde..22da73ca7f 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -40,4 +40,9 @@ + + + + + From d74c34780829d2cd114b55132baad0d3cb8afdc8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 8 Dec 2021 09:38:25 -0600 Subject: [PATCH 0521/1254] Cleanup. Turn off 'latest' tag. Ensure -test image suffixes all tags with -test. --- .github/workflows/docker.yml | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a6a8ae9099..159121f485 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,14 +8,19 @@ jobs: docker: runs-on: ubuntu-latest env: - # Define tags to use for Docker images based on Git tags/branches - # For a new branch commit, tag Docker image with that branch name - # Except for 'main' branch, where we use the literal 'dspace-7_x' tag (see below). - # For a new tag, tag Docker image with that same tag + # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) + # For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image. + # For a new commit on other branches, use the branch name as the tag for Docker image. + # For a new tag, copy that tag name as the tag for Docker image. IMAGE_TAGS: | - type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }} - type=raw,value=dspace-7_x,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} type=ref,event=tag + # Define default tag "flavor" for docker/metadata-action per + # https://github.com/docker/metadata-action#flavor-input + # We turn off 'latest' tag by default. + TAGS_FLAVOR: | + latest=false steps: # https://github.com/actions/checkout @@ -45,6 +50,7 @@ jobs: with: images: dspace/dspace-dependencies tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} # https://github.com/docker/build-push-action - name: Build and push 'dspace-dependencies' image @@ -70,6 +76,7 @@ jobs: with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} - name: Build and push 'dspace' image id: docker_build @@ -93,10 +100,11 @@ jobs: uses: docker/metadata-action@v3 with: images: dspace/dspace - # ONLY add 'dspace-7_x-test' version tag if this is the 'main' branch. - # (No other tags are added for this image, as it's only used for testing/development) - tags: | - type=raw,value=dspace-7_x-test,enable=${{ github.ref == 'refs/heads/main' }} + tags: ${{ env.IMAGE_TAGS }} + # As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same + # tagging logic as the primary 'dspace/dspace' image above. + flavor: ${{ env.TAGS_FLAVOR }} + suffix=-test - name: Build and push 'dspace-test' image id: docker_build_test @@ -121,6 +129,7 @@ jobs: with: images: dspace/dspace-cli tags: ${{ env.IMAGE_TAGS }} + flavor: ${{ env.TAGS_FLAVOR }} - name: Build and push 'dspace-cli' image id: docker_build_cli From a17e34005930a93e8a97ca2e6c8616e552b5bf61 Mon Sep 17 00:00:00 2001 From: Samuel Date: Wed, 8 Dec 2021 17:55:42 +0100 Subject: [PATCH 0522/1254] dspace-angular issue 852 BrowseBy list are statically configured --- .../app/rest/converter/BrowseIndexConverter.java | 1 + .../org/dspace/app/rest/model/BrowseIndexRest.java | 10 ++++++++++ .../dspace/app/rest/matcher/BrowseIndexMatcher.java | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java index 5e4dec63df..6ee836e5fc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java @@ -31,6 +31,7 @@ public class BrowseIndexConverter implements DSpaceConverter metadataList = new ArrayList(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java index 8b2678e005..9fee6cbdba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java @@ -43,6 +43,8 @@ public class BrowseIndexRest extends BaseObjectRest { @JsonProperty(value = "metadata") List metadataList; + String dataType; + List sortOptions; String order; @@ -74,6 +76,14 @@ public class BrowseIndexRest extends BaseObjectRest { this.metadataList = metadataList; } + public String getDataType() { + return dataType; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + public String getOrder() { return order; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java index 1ad8586574..82d611facf 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java @@ -32,6 +32,7 @@ public class BrowseIndexMatcher { return allOf( hasJsonPath("$.metadata", contains("dc.subject.*")), hasJsonPath("$.metadataBrowse", Matchers.is(true)), + hasJsonPath("$.dataType", equalToIgnoringCase("text")), hasJsonPath("$.order", equalToIgnoringCase(order)), hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")), hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/subject")), @@ -44,6 +45,7 @@ public class BrowseIndexMatcher { return allOf( hasJsonPath("$.metadata", contains("dc.title")), hasJsonPath("$.metadataBrowse", Matchers.is(false)), + hasJsonPath("$.dataType", equalToIgnoringCase("title")), hasJsonPath("$.order", equalToIgnoringCase(order)), hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")), hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/title")), @@ -55,6 +57,7 @@ public class BrowseIndexMatcher { return allOf( hasJsonPath("$.metadata", contains("dc.contributor.*", "dc.creator")), hasJsonPath("$.metadataBrowse", Matchers.is(true)), + hasJsonPath("$.dataType", equalToIgnoringCase("text")), hasJsonPath("$.order", equalToIgnoringCase(order)), hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")), hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/author")), @@ -67,6 +70,7 @@ public class BrowseIndexMatcher { return allOf( hasJsonPath("$.metadata", contains("dc.date.issued")), hasJsonPath("$.metadataBrowse", Matchers.is(false)), + hasJsonPath("$.dataType", equalToIgnoringCase("date")), hasJsonPath("$.order", equalToIgnoringCase(order)), hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")), hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/dateissued")), From adb41116db4a7e9442592f6ab9d1839b82a84a52 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 8 Dec 2021 14:22:15 -0600 Subject: [PATCH 0523/1254] Disable docker action on forked repos. Only run for 'dspace/dspace' --- .github/workflows/docker.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 159121f485..3b728b12d0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,6 +6,8 @@ on: [push, pull_request] jobs: docker: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' runs-on: ubuntu-latest env: # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) From 4875e2c4d6841ef4f21b376efed06e700c9eb9bd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 8 Dec 2021 14:29:05 -0600 Subject: [PATCH 0524/1254] Limit branches/tags to ones starting with "dspace-", as that's our standard release/maintenance branch format --- .github/workflows/docker.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3b728b12d0..34539abc16 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,8 +1,16 @@ # DSpace Docker image build for hub.docker.com name: Docker images -# Run this Build for all pushes / PRs to current branch -on: [push, pull_request] +# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases. +# Also run for PRs to ensure PR doesn't break Docker build process +on: + push: + branches: + - main + - 'dspace-**' + tags: + - 'dspace-**' + pull_request: jobs: docker: @@ -29,6 +37,7 @@ jobs: - name: Checkout codebase uses: actions/checkout@v2 + # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx uses: docker/setup-buildx-action@v1 From 0a04bc739d7f6ff521aa288fca0067b069fc17bf Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 8 Dec 2021 14:08:32 -0600 Subject: [PATCH 0525/1254] Minor license formatting cleanup to allow GitHub to recognize our BSD 3-Clause License. Move third-party licenses notice to NOTICE file. --- LICENSE | 20 ++++++-------------- NOTICE | 10 ++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/LICENSE b/LICENSE index f55d21fe42..b381f6d968 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -DSpace source code BSD License: +BSD 3-Clause License Copyright (c) 2002-2021, LYRASIS. All rights reserved. @@ -13,13 +13,12 @@ notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- Neither the name DuraSpace nor the name of the DSpace Foundation -nor the names of its contributors may be used to endorse or promote -products derived from this software without specific prior written -permission. +- Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, @@ -29,11 +28,4 @@ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - - -DSpace uses third-party libraries which may be distributed under -different licenses to the above. Information about these licenses -is detailed in the LICENSES_THIRD_PARTY file at the root of the source -tree. You must agree to the terms of these licenses, in addition to -the above DSpace source code license, in order to use this software. +DAMAGE. \ No newline at end of file diff --git a/NOTICE b/NOTICE index 6743fea511..010c89a4bb 100644 --- a/NOTICE +++ b/NOTICE @@ -1,3 +1,13 @@ +Licenses of Third-Party Libraries +================================= + +DSpace uses third-party libraries which may be distributed under +different licenses than specified in our LICENSE file. Information +about these licenses is detailed in the LICENSES_THIRD_PARTY file at +the root of the source tree. You must agree to the terms of these +licenses, in addition to the DSpace source code license, in order to +use this software. + Licensing Notices ================= From 300bcaa1daefa1621dd0f31afa299563af2b349e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 8 Dec 2021 14:15:38 -0600 Subject: [PATCH 0526/1254] Add to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index caca04d0a9..864a099c1d 100644 --- a/README.md +++ b/README.md @@ -136,3 +136,6 @@ run automatically by [GitHub Actions](https://github.com/DSpace/DSpace/actions?q DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause). The full license is available in the [LICENSE](LICENSE) file or online at http://www.dspace.org/license/ + +DSpace uses third-party libraries which may be distributed under different licenses. Those licenses are listed +in the [LICENSES_THIRD_PARTY](LICENSES_THIRD_PARTY) file. From cc15f414b3bf69d83b34de5ebaa7ae8c5aa94302 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 9 Dec 2021 15:39:31 +0100 Subject: [PATCH 0527/1254] [CST-4882] System.out.println() removed --- .../main/java/org/dspace/app/rest/DiscoveryRestController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java index 931cf9f126..9a77908c46 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java @@ -198,7 +198,6 @@ public class DiscoveryRestController implements InitializingBean { configuration, List searchFilters, Pageable page) throws Exception { - System.out.println("FACETS/xxxxx"); dsoTypes = emptyIfNull(dsoTypes); @@ -220,7 +219,6 @@ public class DiscoveryRestController implements InitializingBean { halLinkService.addLinks(facetResultsResource, page); return facetResultsResource; } catch (Exception e) { - System.out.println("ERROR = " + e.getMessage()); boolean isParsingException = e.getMessage().contains(SOLR_PARSE_ERROR_MESSAGE); if (isParsingException) { throw new UnprocessableEntityException(e.getMessage()); From c40519568025cec6df39a02ac1e70b8504cc8369 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 9 Dec 2021 12:52:55 -0800 Subject: [PATCH 0528/1254] Initial work on bitstream IIIF processor. --- .../canvasdimension/CanvasDimensionCLI.java | 130 ++++++++++ .../IIIFCanvasDimensionProcessor.java | 238 ++++++++++++++++++ .../canvasdimension/ImageDimensionReader.java | 30 +++ 3 files changed, 398 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java new file mode 100644 index 0000000000..836f355991 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.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.canvasdimension; + +import java.util.Arrays; + +import org.apache.commons.cli.*; +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.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; + +public class CanvasDimensionCLI { + + private CanvasDimensionCLI() {} + + public static void main(String[] argv) throws Exception { + + boolean force = false; + String identifier = null; + int max2Process = Integer.MAX_VALUE; + + Context context = new Context(); + IIIFCanvasDimensionProcessor canvasProcessor = new IIIFCanvasDimensionProcessor(); + + CommandLineParser parser = new DefaultParser(); + + Options options = new Options(); + options.addOption("i", "identifier", true, + "process IIIF canvas dimensions for images belonging to identifier"); + options.addOption("f", "force", false, + "force update of all IIIF canvas height and width dimensions"); + options.addOption("s", "skipList", false, + "force update of all IIIF canvas height and width dimensions"); + options.addOption("m", "maximum", true, + "process no more than maximum items"); + //create a "skip" option (to specify communities/collections/items to skip) + Option skipOption = Option.builder("s") + .longOpt("skip") + .hasArg() + .hasArgs() + .valueSeparator(',') + .desc( + "SKIP the bitstreams belonging to identifier\n" + + "Separate multiple identifiers with a comma (,)\n" + + "(e.g. -s \n 123456789/34,123456789/323)") + .build(); + options.addOption(skipOption); + + CommandLine line = null; + try { + line = parser.parse(options, argv); + } catch (MissingArgumentException e) { + System.out.println("ERROR: " + e.getMessage()); + HelpFormatter myhelp = new HelpFormatter(); + myhelp.printHelp("CanvasDimension processor\n", options); + System.exit(1); + } + + if (line.hasOption('f')) { + force = true; + } + if (line.hasOption('i')) { + identifier = line.getOptionValue('i'); + } else { + System.out.println("An identifier for a Community, Collection, or Item must be provided."); + } + if (line.hasOption('m')) { + max2Process = Integer.parseInt(line.getOptionValue('m')); + if (max2Process <= 1) { + System.out.println("Invalid maximum value '" + + line.getOptionValue('m') + "' - ignoring"); + max2Process = Integer.MAX_VALUE; + } + } + String skipIds[] = null; + + if (line.hasOption('s')) { + //specified which identifiers to skip when processing + skipIds = line.getOptionValues('s'); + + if (skipIds == null || skipIds.length == 0) { //display error, since no identifiers specified to skip + System.err.println("\nERROR: -s (-skip) option requires at least one identifier to SKIP.\n" + + "Make sure to separate multiple identifiers with a comma!\n" + + "(e.g. -s 123456789/34,123456789/323)\n"); + HelpFormatter myhelp = new HelpFormatter(); + myhelp.printHelp("MediaFilterManager\n", options); + System.exit(0); + } + canvasProcessor.setSkipList(Arrays.asList(skipIds)); + } + + + DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, identifier); + if (dso == null) { + throw new IllegalArgumentException("Cannot resolve " + + identifier + " to a DSpace object"); + } + + + canvasProcessor.setForceProcessing(force); + canvasProcessor.setMax2Process(max2Process); + + switch (dso.getType()) { + case Constants.SITE: + canvasProcessor.processSite(context); + break; + case Constants.COMMUNITY: + canvasProcessor.processCommunity(context, (Community) dso); + break; + case Constants.COLLECTION: + canvasProcessor.processCollection(context, (Collection) dso); + break; + case Constants.ITEM: + canvasProcessor.processItem(context, (Item) dso); + break; + default: + break; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java new file mode 100644 index 0000000000..8541ab6668 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java @@ -0,0 +1,238 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.canvasdimension; + +import java.io.InputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.CommunityService; +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.license.CreativeCommonsServiceImpl; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +public class IIIFCanvasDimensionProcessor implements InitializingBean { + + @Autowired + ItemService itemService; + @Autowired + CommunityService communityService; + @Autowired + BitstreamService bitstreamService; + @Autowired + DSpaceObjectService dSpaceObjectService; + + // metadata used to enable the iiif features on the item + public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; + private static final String IIIF_WIDTH_METADATA = "iiif.image.width"; + // The DSpace bundle for other content related to item. + protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; + + private boolean forceProcessing = false; + private boolean isQuiet = false; + private List skipList = null; + private int max2Process = Integer.MAX_VALUE; // TODO no option for this yet. + private int processed = 0; + protected Item currentItem = null; // TODO needed? + + @Override + public void afterPropertiesSet() throws Exception { + + } + + public void setForceProcessing(boolean force) { + forceProcessing = force; + } + + public void setMax2Process(int max2Process) { + this.max2Process = max2Process; + } + + public void setSkipList(List skipList) { + this.skipList = skipList; + } + + public void processSite(Context context) throws Exception { + if (skipList != null) { + //if a skip-list exists, we need to filter community-by-community + //so we can respect what is in the skip-list + List topLevelCommunities = communityService.findAllTop(context); + + for (Community topLevelCommunity : topLevelCommunities) { + processCommunity(context, topLevelCommunity); + } + } else { + //otherwise, just find every item and process + Iterator itemIterator = itemService.findAll(context); + while (itemIterator.hasNext() && processed < max2Process) { + processItem(context, itemIterator.next()); + } + } + } + + public void processCommunity(Context context, Community community) throws Exception { + if (!inSkipList(community.getHandle())) { + List subcommunities = community.getSubcommunities(); + for (Community subcommunity : subcommunities) { + processCommunity(context, subcommunity); + } + List collections = community.getCollections(); + for (Collection collection : collections) { + processCollection(context, collection); + } + } + } + + public void processCollection(Context context, Collection collection) throws Exception { + if (!inSkipList(collection.getHandle())) { + Iterator itemIterator = itemService.findAllByCollection(context, collection); + while (itemIterator.hasNext() && processed < max2Process) { + processItem(context, itemIterator.next()); + } + } + } + + public void processItem(Context context, Item item) throws Exception { + if (!inSkipList(item.getHandle())) { + boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') + .contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")); + if (isIIIFItem) { + if (processBundles(context, item)) { + ++processed; + } + } + } + } + + private boolean processBundles(Context context, Item item) throws Exception { + List bundles = getIIIFBundles(item); + boolean done = false; + for (Bundle bundle : bundles) { + List myBitstreams = bundle.getBitstreams(); + for (Bitstream myBitstream : myBitstreams) { + done |= processBitstream(context, item, myBitstream); + } + } + return done; + + } + + private boolean processBitstream(Context context, Item item, Bitstream bitstream) throws Exception { + boolean processed = false; + boolean isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); + if (isImage) { + Optional op = bitstream.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') + .contentEquals(IIIF_WIDTH_METADATA)).findFirst(); + if (op.isEmpty() || forceProcessing) { + InputStream srcStream = bitstreamService.retrieve(context, bitstream); + int[] dims = ImageDimensionReader.getImageDimensions(srcStream); + if (dims != null) { + processed = setBitstreamMetadata(context, bitstream, dims); + } + } + } + return processed; + } + + private boolean setBitstreamMetadata(Context context, Bitstream bitstream, int[] dims) { + try { + dSpaceObjectService.clearMetadata(context, bitstream, "iiif", + "image", "width", Item.ANY); + dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", + "image", "width", null, + String.valueOf(dims[0]), null, -1, -1); + dSpaceObjectService.clearMetadata(context, bitstream, "iiif", + "image", "height", Item.ANY); + dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", + "image", "height", null, + String.valueOf(dims[1]), null, -1, -1); + return true; + } catch (SQLException e) { + System.out.println("Unable to update metadata: " + e.getMessage()); + return false; + } + } + + private boolean inSkipList(String identifier) { + if (skipList != null && skipList.contains(identifier)) { + if (!isQuiet) { + System.out.println("SKIP-LIST: skipped bitstreams within identifier " + identifier); + } + return true; + } else { + return false; + } + } + + /** + * This method returns the bundles holding IIIF resources if any. + * If there is no IIIF content available an empty bundle list is returned. + * @param item the DSpace item + * + * @return list of DSpace bundles with IIIF content + */ + private List getIIIFBundles(Item item) { + boolean iiif = isIIIFEnabled(item); + List bundles = new ArrayList<>(); + if (iiif) { + bundles = item.getBundles().stream().filter(b -> isIIIFBundle(b)).collect(Collectors.toList()); + } + return bundles; + } + + /** + * This method verify if the IIIF feature is enabled on the item or parent collection. + * + * @param item the dspace item + * @return true if the item supports IIIF + */ + private boolean isIIIFEnabled(Item item) { + return item.getOwningCollection().getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")) + || item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")); + } + + /** + * Utility method to check is a bundle can contain bitstreams to use as IIIF + * resources + * + * @param b the DSpace bundle to check + * @return true if the bundle can contain bitstreams to use as IIIF resources + */ + private boolean isIIIFBundle(Bundle b) { + return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, + Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", + "BRANDED_PREVIEW", "TEXT", OTHER_CONTENT_BUNDLE) + && b.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java new file mode 100644 index 0000000000..bb7e2389c2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.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.canvasdimension; + +import java.awt.image.BufferedImage; +import java.io.InputStream; +import javax.imageio.ImageIO; + +public class ImageDimensionReader { + + private ImageDimensionReader() {} + + public static int[] getImageDimensions(InputStream image) throws Exception { + int[] dims = new int[2]; + BufferedImage buf = ImageIO.read(image); + int width = buf.getWidth(); + int height = buf.getHeight(); + if (width > 0 && height > 0) { + dims[0] = width; + dims[1] = height; + return dims; + } + return null; + } +} From bdbba6d323e33a4ea07b4865a77e0896c8857041 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 9 Dec 2021 13:05:17 -0800 Subject: [PATCH 0529/1254] Added more options. --- .../canvasdimension/CanvasDimensionCLI.java | 7 +++ .../IIIFCanvasDimensionProcessor.java | 60 ++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java index 836f355991..54eea253f5 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java @@ -25,6 +25,7 @@ public class CanvasDimensionCLI { public static void main(String[] argv) throws Exception { boolean force = false; + boolean isQuiet = false; String identifier = null; int max2Process = Integer.MAX_VALUE; @@ -38,6 +39,8 @@ public class CanvasDimensionCLI { "process IIIF canvas dimensions for images belonging to identifier"); options.addOption("f", "force", false, "force update of all IIIF canvas height and width dimensions"); + options.addOption("q", "quiet", false, + "do not print anything except in the event of errors."); options.addOption("s", "skipList", false, "force update of all IIIF canvas height and width dimensions"); options.addOption("m", "maximum", true, @@ -68,6 +71,9 @@ public class CanvasDimensionCLI { if (line.hasOption('f')) { force = true; } + if (line.hasOption('q')) { + isQuiet = true; + } if (line.hasOption('i')) { identifier = line.getOptionValue('i'); } else { @@ -108,6 +114,7 @@ public class CanvasDimensionCLI { canvasProcessor.setForceProcessing(force); canvasProcessor.setMax2Process(max2Process); + canvasProcessor.setIsQuiet(isQuiet); switch (dso.getType()) { case Constants.SITE: diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java index 8541ab6668..c647ce8741 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java @@ -60,18 +60,40 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } + /** + * Set the force processing property. If true, existing canvas + * metadata will be replaced. + * @param force + */ public void setForceProcessing(boolean force) { forceProcessing = force; } + public void setIsQuiet(boolean quiet) { + isQuiet = quiet; + } + + /** + * Set the maximum number of items to process. + * @param max2Process + */ public void setMax2Process(int max2Process) { this.max2Process = max2Process; } + /** + * Set dso identifiers to skip. + * @param skipList + */ public void setSkipList(List skipList) { this.skipList = skipList; } + /** + * Set IIIF canvas dimensions on all IIIF items in the site. + * @param context + * @throws Exception + */ public void processSite(Context context) throws Exception { if (skipList != null) { //if a skip-list exists, we need to filter community-by-community @@ -90,6 +112,13 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } } + /** + * Set IIIF canvas dimensions on all IIIF items in a community and its + * sub-communities. + * @param context + * @param community + * @throws Exception + */ public void processCommunity(Context context, Community community) throws Exception { if (!inSkipList(community.getHandle())) { List subcommunities = community.getSubcommunities(); @@ -103,6 +132,12 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } } + /** + * Set IIIF canvas dimensions on all IIIF items in a collection. + * @param context + * @param collection + * @throws Exception + */ public void processCollection(Context context, Collection collection) throws Exception { if (!inSkipList(collection.getHandle())) { Iterator itemIterator = itemService.findAllByCollection(context, collection); @@ -112,6 +147,12 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } } + /** + * Set IIIF canvas dimensions for an item. + * @param context + * @param item + * @throws Exception + */ public void processItem(Context context, Item item) throws Exception { if (!inSkipList(item.getHandle())) { boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') @@ -126,20 +167,35 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } } + /** + * Process all IIIF bundles for an item. + * @param context + * @param item + * @return + * @throws Exception + */ private boolean processBundles(Context context, Item item) throws Exception { List bundles = getIIIFBundles(item); boolean done = false; for (Bundle bundle : bundles) { List myBitstreams = bundle.getBitstreams(); for (Bitstream myBitstream : myBitstreams) { - done |= processBitstream(context, item, myBitstream); + done |= processBitstream(context, myBitstream); } } return done; } - private boolean processBitstream(Context context, Item item, Bitstream bitstream) throws Exception { + /** + * Sets the IIIF height and width metadata for all images. If width metadata already exists, + * the bitstream is processed only if forceProcessing is true. + * @param context + * @param bitstream + * @return + * @throws Exception + */ + private boolean processBitstream(Context context, Bitstream bitstream) throws Exception { boolean processed = false; boolean isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); if (isImage) { From 98b79d436e035322931a148329e6c2c624e1a3bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Dec 2021 01:03:33 +0000 Subject: [PATCH 0530/1254] Bump log4j-core from 2.13.3 to 2.15.0 Bumps log4j-core from 2.13.3 to 2.15.0. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-core 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 0502eea5b0..ff129f0a8c 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ 2.3.1 9.4.41.v20210516 - 2.13.3 + 2.15.0 2.0.24 3.17 1.7.25 From f837bee8782a3202e99a345b01c4be565990f6a0 Mon Sep 17 00:00:00 2001 From: Santiago Tettamanti Date: Fri, 10 Dec 2021 11:24:28 -0300 Subject: [PATCH 0531/1254] Fix findSubmitAuthorized endpoint to support queries by starts with --- .../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 de29b8026a..e54f609389 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -1010,7 +1010,7 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i if (StringUtils.isNotBlank(q)) { StringBuilder buildQuery = new StringBuilder(); String escapedQuery = ClientUtils.escapeQueryChars(q); - buildQuery.append(escapedQuery).append(" OR ").append(escapedQuery).append("*"); + buildQuery.append("(").append(escapedQuery).append(" OR ").append(escapedQuery).append("*").append(")"); discoverQuery.setQuery(buildQuery.toString()); } DiscoverResult resp = searchService.search(context, discoverQuery); From 6381644eeee4c92a865507d5c8d9519e178bda86 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 10 Dec 2021 17:29:14 +0100 Subject: [PATCH 0532/1254] [CST-4882] Error handling improved --- .../org/dspace/app/rest/DiscoveryRestController.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java index 9a77908c46..5ecbe19176 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java @@ -53,7 +53,7 @@ public class DiscoveryRestController implements InitializingBean { private static final Logger log = LogManager.getLogger(); - private static final String SOLR_PARSE_ERROR_MESSAGE = "Cannot parse"; + private static final String SOLR_PARSE_ERROR_CLASS = "org.apache.solr.search.SyntaxError"; @Autowired protected Utils utils; @@ -161,7 +161,7 @@ public class DiscoveryRestController implements InitializingBean { halLinkService.addLinks(searchResultsResource, page); return searchResultsResource; } catch (IllegalArgumentException e) { - boolean isParsingException = e.getMessage().contains(SOLR_PARSE_ERROR_MESSAGE); + boolean isParsingException = e.getMessage().contains(SOLR_PARSE_ERROR_CLASS); if (isParsingException) { throw new UnprocessableEntityException(e.getMessage()); } else { @@ -219,7 +219,12 @@ public class DiscoveryRestController implements InitializingBean { halLinkService.addLinks(facetResultsResource, page); return facetResultsResource; } catch (Exception e) { - boolean isParsingException = e.getMessage().contains(SOLR_PARSE_ERROR_MESSAGE); + boolean isParsingException = e.getMessage().contains(SOLR_PARSE_ERROR_CLASS); + /* + * We unfortunately have to do a string comparison to locate the source of the error, as Solr only sends + * back a generic exception, and the org.apache.solr.search.SyntaxError is only available as plain text + * in the error message. + */ if (isParsingException) { throw new UnprocessableEntityException(e.getMessage()); } else { From fc5d3bcd17b6f8c4f823517880bb3c5d6a31ffef Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 10 Dec 2021 11:48:57 -0500 Subject: [PATCH 0533/1254] Clarify the need to manage dependencies for mockserver. #7986 --- dspace-api/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index f3d39e8357..d453407fc4 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -943,6 +943,8 @@ + org.apache.commons commons-text From e27a10978966db47097fb5a1a2e33b8cdbb76d88 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 10 Dec 2021 11:50:28 -0500 Subject: [PATCH 0534/1254] Generalize HttpConnectionPoolService for multiple instances with separate configuration. This removes Solr-specific configuration property names from the otherwise general service implementation by adding a bean property to specify a configuration property prefix. --- .../authority/AuthoritySolrServiceImpl.java | 3 ++- .../org/dspace/discovery/SolrSearchCore.java | 3 ++- .../impl/HttpConnectionPoolService.java | 21 ++++++++++++------- .../dspace/statistics/SolrStatisticsCore.java | 4 +++- .../impl/solr/DSpaceSolrServerResolver.java | 4 +++- .../dspace/xoai/solr/DSpaceSolrServer.java | 3 ++- dspace/config/spring/api/core-services.xml | 6 +++++- 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java index 860630e8c2..3cc9500f97 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java @@ -12,6 +12,7 @@ import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import javax.inject.Named; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -37,7 +38,7 @@ public class AuthoritySolrServiceImpl implements AuthorityIndexingService, Autho private static final Logger log = LogManager.getLogger(AuthoritySolrServiceImpl.class); - @Inject + @Inject @Named("solr") private HttpConnectionPoolService httpConnectionPoolService; protected AuthoritySolrServiceImpl() { diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java index dac719695e..0874371027 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java @@ -8,6 +8,7 @@ package org.dspace.discovery; import java.io.IOException; +import javax.inject.Named; import org.apache.commons.validator.routines.UrlValidator; import org.apache.logging.log4j.LogManager; @@ -35,7 +36,7 @@ public class SolrSearchCore { protected IndexingService indexingService; @Autowired protected ConfigurationService configurationService; - @Autowired + @Autowired @Named("solr") protected HttpConnectionPoolService httpConnectionPoolService; /** diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java index a1f9aaf16d..11cb6bc52d 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java @@ -37,10 +37,9 @@ public class HttpConnectionPoolService { @Inject ConfigurationService configurationService; - private PoolingHttpClientConnectionManager connManager; - - private final ConnectionKeepAliveStrategy keepAliveStrategy - = new KeepAliveStrategy(); + /** Configuration properties will begin with this string. */ + @Inject + String configPrefix; /** Maximum number of concurrent pooled connections. */ private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; @@ -62,16 +61,21 @@ public class HttpConnectionPoolService { /** Connection idle if unused for this long: seconds */ private static final int IDLE_INTERVAL = 30; + private PoolingHttpClientConnectionManager connManager; + + private final ConnectionKeepAliveStrategy keepAliveStrategy + = new KeepAliveStrategy(); + @PostConstruct protected void init() { connManager = new PoolingHttpClientConnectionManager( - configurationService.getIntProperty("solrClient.timeToLive", DEFAULT_TTL), + configurationService.getIntProperty(configPrefix + ".client.timeToLive", DEFAULT_TTL), TimeUnit.SECONDS); connManager.setMaxTotal(configurationService.getIntProperty( - "solrClient.maxTotalConnections", DEFAULT_MAX_TOTAL_CONNECTIONS)); + configPrefix + ".client.maxTotalConnections", DEFAULT_MAX_TOTAL_CONNECTIONS)); connManager.setDefaultMaxPerRoute( - configurationService.getIntProperty("solrClient.maxPerRoute", + configurationService.getIntProperty(configPrefix + ".client.maxPerRoute", DEFAULT_MAX_PER_ROUTE)); Thread connectionMonitor = new IdleConnectionMonitorThread(connManager); @@ -114,7 +118,8 @@ public class HttpConnectionPoolService { } } - return configurationService.getIntProperty("solrClient.keepAlive", + // If server did not request keep-alive, use configured value. + return configurationService.getIntProperty(configPrefix + ".client.keepAlive", DEFAULT_KEEPALIVE); } } diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java b/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java index 9d60913e98..38a170bda3 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java @@ -9,6 +9,8 @@ package org.dspace.statistics; import static org.apache.logging.log4j.LogManager.getLogger; +import javax.inject.Named; + import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.impl.HttpSolrClient; @@ -28,7 +30,7 @@ public class SolrStatisticsCore { @Autowired private ConfigurationService configurationService; - @Autowired + @Autowired @Named("solr") private HttpConnectionPoolService httpConnectionPoolService; /** diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java index ee6380ca1d..0619577bfc 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java @@ -7,6 +7,8 @@ */ package org.dspace.xoai.services.impl.solr; +import javax.inject.Named; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; @@ -24,7 +26,7 @@ public class DSpaceSolrServerResolver implements SolrServerResolver { @Autowired private ConfigurationService configurationService; - @Autowired + @Autowired @Named("solr") private HttpConnectionPoolService httpConnectionPoolService; @Override diff --git a/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java b/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java index 25b1a87592..bf6b46807b 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/solr/DSpaceSolrServer.java @@ -37,7 +37,8 @@ public class DSpaceSolrServer { HttpConnectionPoolService httpConnectionPoolService = DSpaceServicesFactory.getInstance() .getServiceManager() - .getServiceByName(null, HttpConnectionPoolService.class); + .getServiceByName("solrHttpConnectionPoolService", + HttpConnectionPoolService.class); String serverUrl = configurationService.getProperty("oai.solr.url"); try { _server = new HttpSolrClient.Builder(serverUrl) diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index cc4fe29817..6018c2401b 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -64,7 +64,11 @@ + id='solrHttpConnectionPoolService' + scope='singleton'> + + + From 171245a144f247ef56f3b391de925bdf34e78754 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 10 Dec 2021 14:36:22 -0500 Subject: [PATCH 0535/1254] Autowire with 'id' not : @Named doesn't find qualifiers. #7986 --- .../dspace/authority/AuthoritySolrServiceImpl.java | 2 +- .../java/org/dspace/discovery/SolrSearchCore.java | 2 +- .../service/impl/HttpConnectionPoolService.java | 12 ++++++++++-- .../org/dspace/statistics/SolrStatisticsCore.java | 2 +- .../service/impl/HttpConnectionPoolServiceTest.java | 2 +- .../services/impl/solr/DSpaceSolrServerResolver.java | 2 +- dspace/config/spring/api/core-services.xml | 6 +++--- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java index 3cc9500f97..dab8cd5b2e 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java @@ -38,7 +38,7 @@ public class AuthoritySolrServiceImpl implements AuthorityIndexingService, Autho private static final Logger log = LogManager.getLogger(AuthoritySolrServiceImpl.class); - @Inject @Named("solr") + @Inject @Named("solrHttpConnectionPoolService") private HttpConnectionPoolService httpConnectionPoolService; protected AuthoritySolrServiceImpl() { diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java index 0874371027..b430a0c973 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java @@ -36,7 +36,7 @@ public class SolrSearchCore { protected IndexingService indexingService; @Autowired protected ConfigurationService configurationService; - @Autowired @Named("solr") + @Autowired @Named("solrHttpConnectionPoolService") protected HttpConnectionPoolService httpConnectionPoolService; /** diff --git a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java index 11cb6bc52d..4f1f734988 100644 --- a/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java +++ b/dspace-api/src/main/java/org/dspace/service/impl/HttpConnectionPoolService.java @@ -38,8 +38,7 @@ public class HttpConnectionPoolService { ConfigurationService configurationService; /** Configuration properties will begin with this string. */ - @Inject - String configPrefix; + private final String configPrefix; /** Maximum number of concurrent pooled connections. */ private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; @@ -66,6 +65,15 @@ public class HttpConnectionPoolService { private final ConnectionKeepAliveStrategy keepAliveStrategy = new KeepAliveStrategy(); + /** + * Construct a pool for a given set of configuration properties. + * + * @param configPrefix Configuration property names will begin with this. + */ + public HttpConnectionPoolService(String configPrefix) { + this.configPrefix = configPrefix; + } + @PostConstruct protected void init() { connManager = new PoolingHttpClientConnectionManager( diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java b/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java index 38a170bda3..9ad72cbf31 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrStatisticsCore.java @@ -30,7 +30,7 @@ public class SolrStatisticsCore { @Autowired private ConfigurationService configurationService; - @Autowired @Named("solr") + @Autowired @Named("solrHttpConnectionPoolService") private HttpConnectionPoolService httpConnectionPoolService; /** diff --git a/dspace-api/src/test/java/org/dspace/service/impl/HttpConnectionPoolServiceTest.java b/dspace-api/src/test/java/org/dspace/service/impl/HttpConnectionPoolServiceTest.java index 50d20c9a3c..7fb6982ae3 100644 --- a/dspace-api/src/test/java/org/dspace/service/impl/HttpConnectionPoolServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/service/impl/HttpConnectionPoolServiceTest.java @@ -62,7 +62,7 @@ public class HttpConnectionPoolServiceTest configurationService.setProperty("solrClient.maxTotalConnections", 2); configurationService.setProperty("solrClient.maxPerRoute", 2); - HttpConnectionPoolService instance = new HttpConnectionPoolService(); + HttpConnectionPoolService instance = new HttpConnectionPoolService("solr"); instance.configurationService = configurationService; instance.init(); diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java index 0619577bfc..6637d7f26f 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/solr/DSpaceSolrServerResolver.java @@ -26,7 +26,7 @@ public class DSpaceSolrServerResolver implements SolrServerResolver { @Autowired private ConfigurationService configurationService; - @Autowired @Named("solr") + @Autowired @Named("solrHttpConnectionPoolService") private HttpConnectionPoolService httpConnectionPoolService; @Override diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 6018c2401b..526c448b46 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -65,9 +65,9 @@ - - + scope='singleton' + autowire-candidate='true'> + From 34a200b8c86bbf7709fe9d391beaccb9175bd6e8 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 10 Dec 2021 17:14:17 -0800 Subject: [PATCH 0536/1254] Added added service for image formats not supported by ImageIO (jp2) --- .../canvasdimension/CanvasDimensionCLI.java | 42 +++++++++++- .../IIIFApiQueryServiceImpl.java | 65 ++++++++++++++++++ ...va => IIIFCanvasDimensionServiceImpl.java} | 68 ++++++++++++------- .../canvasdimension/ImageDimensionReader.java | 4 +- .../IIIFCanvasDimensionServiceFactory.java | 15 ++++ ...IIIFCanvasDimensionServiceFactoryImpl.java | 15 ++++ .../service/IIIFApiQueryService.java | 9 +++ .../service/IIIFCanvasDimensionService.java | 30 ++++++++ dspace/config/launcher.xml | 7 ++ .../spring/api/core-factory-services.xml | 1 + dspace/config/spring/api/core-services.xml | 3 + 11 files changed, 230 insertions(+), 29 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java rename dspace-api/src/main/java/org/dspace/app/canvasdimension/{IIIFCanvasDimensionProcessor.java => IIIFCanvasDimensionServiceImpl.java} (82%) create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java index 54eea253f5..4b560390ca 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java @@ -8,18 +8,30 @@ package org.dspace.app.canvasdimension; import java.util.Arrays; +import java.util.UUID; import org.apache.commons.cli.*; +import org.dspace.app.canvasdimension.factory.IIIFCanvasDimensionServiceFactory; +import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; 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.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; public class CanvasDimensionCLI { + private static final EPersonService epersonService = EPersonServiceFactory.getInstance().getEPersonService(); + private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance() + .getConfigurationService(); + private CanvasDimensionCLI() {} public static void main(String[] argv) throws Exception { @@ -27,16 +39,26 @@ public class CanvasDimensionCLI { boolean force = false; boolean isQuiet = false; String identifier = null; + String eperson = null; int max2Process = Integer.MAX_VALUE; + boolean iiifEnabled = configurationService.getBooleanProperty("iiif.enabled"); + + if (!iiifEnabled) { + System.out.println("IIIF is not enabled on this DSpace server."); + return; + } Context context = new Context(); - IIIFCanvasDimensionProcessor canvasProcessor = new IIIFCanvasDimensionProcessor(); + IIIFCanvasDimensionService canvasProcessor = IIIFCanvasDimensionServiceFactory.getInstance() + .getIiifCanvasDimensionService(); CommandLineParser parser = new DefaultParser(); Options options = new Options(); options.addOption("i", "identifier", true, "process IIIF canvas dimensions for images belonging to identifier"); + options.addOption("e", "eperson", true, + "email of eperson setting canvas dimensions"); options.addOption("f", "force", false, "force update of all IIIF canvas height and width dimensions"); options.addOption("q", "quiet", false, @@ -74,6 +96,9 @@ public class CanvasDimensionCLI { if (line.hasOption('q')) { isQuiet = true; } + if (line.hasOption('e')) { + eperson = line.getOptionValue('e'); + } if (line.hasOption('i')) { identifier = line.getOptionValue('i'); } else { @@ -111,6 +136,21 @@ public class CanvasDimensionCLI { + identifier + " to a DSpace object"); } + EPerson user; + + if (eperson.indexOf('@') != -1) { + // @ sign, must be an email + user = epersonService.findByEmail(context, eperson); + } else { + user = epersonService.find(context, UUID.fromString(eperson)); + } + + if (user == null) { + System.out.println("Error, eperson cannot be found: " + eperson); + System.exit(1); + } + + context.setCurrentUser(user); canvasProcessor.setForceProcessing(force); canvasProcessor.setMax2Process(max2Process); diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java new file mode 100644 index 0000000000..b31658d535 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java @@ -0,0 +1,65 @@ +package org.dspace.app.canvasdimension; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.canvasdimension.service.IIIFApiQueryService; +import org.dspace.content.Bitstream; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +public class IIIFApiQueryServiceImpl implements IIIFApiQueryService, InitializingBean { + + @Autowired(required = true) + protected ConfigurationService configurationService; + + String iiifImageServer; + + @Override + public void afterPropertiesSet() throws Exception { + iiifImageServer = configurationService.getProperty("iiif.image.server"); + } + + public int[] getImageDimensions(Bitstream bitstream) { + return getIiifImageDimensions(bitstream); + } + + /** + * Retrieves image dimensions from the image server (IIIF Image API v.2.1.1). + * @param bitstream the bitstream DSO + * @return image dimensions + */ + private int[] getIiifImageDimensions(Bitstream bitstream) { + int[] arr = new int[2]; + String path = iiifImageServer + bitstream.getID() + "/info.json"; + URL url; + try { + url = new URL(path); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuilder response = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + JsonNode parent = new ObjectMapper().readTree(response.toString()); + arr[0] = parent.get("width").asInt(); + arr[1] = parent.get("height").asInt(); + return arr; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java similarity index 82% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java rename to dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java index c647ce8741..29b08ba5d2 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java @@ -16,11 +16,14 @@ import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.canvasdimension.service.IIIFApiQueryService; +import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.MetadataValue; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectService; @@ -28,19 +31,20 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.license.CreativeCommonsServiceImpl; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; -public class IIIFCanvasDimensionProcessor implements InitializingBean { +public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionService { - @Autowired + @Autowired(required = true) ItemService itemService; - @Autowired + @Autowired(required = true) CommunityService communityService; - @Autowired + @Autowired(required = true) BitstreamService bitstreamService; - @Autowired + @Autowired(required = true) DSpaceObjectService dSpaceObjectService; + @Autowired(required = true) + IIIFApiQueryService iiifApiQuery; // metadata used to enable the iiif features on the item public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; @@ -55,20 +59,17 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { private int processed = 0; protected Item currentItem = null; // TODO needed? - @Override - public void afterPropertiesSet() throws Exception { - - } - /** * Set the force processing property. If true, existing canvas * metadata will be replaced. * @param force */ + @Override public void setForceProcessing(boolean force) { forceProcessing = force; } + @Override public void setIsQuiet(boolean quiet) { isQuiet = quiet; } @@ -77,6 +78,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * Set the maximum number of items to process. * @param max2Process */ + @Override public void setMax2Process(int max2Process) { this.max2Process = max2Process; } @@ -85,6 +87,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * Set dso identifiers to skip. * @param skipList */ + @Override public void setSkipList(List skipList) { this.skipList = skipList; } @@ -94,6 +97,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @param context * @throws Exception */ + @Override public void processSite(Context context) throws Exception { if (skipList != null) { //if a skip-list exists, we need to filter community-by-community @@ -119,6 +123,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @param community * @throws Exception */ + @Override public void processCommunity(Context context, Community community) throws Exception { if (!inSkipList(community.getHandle())) { List subcommunities = community.getSubcommunities(); @@ -138,6 +143,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @param collection * @throws Exception */ + @Override public void processCollection(Context context, Collection collection) throws Exception { if (!inSkipList(collection.getHandle())) { Iterator itemIterator = itemService.findAllByCollection(context, collection); @@ -153,6 +159,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @param item * @throws Exception */ + @Override public void processItem(Context context, Item item) throws Exception { if (!inSkipList(item.getHandle())) { boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') @@ -160,8 +167,10 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || m.getValue().equalsIgnoreCase("yes")); if (isIIIFItem) { - if (processBundles(context, item)) { + if (processItemBundles(context, item)) { ++processed; + // commit changes + context.commit(); } } } @@ -174,15 +183,16 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @return * @throws Exception */ - private boolean processBundles(Context context, Item item) throws Exception { + private boolean processItemBundles(Context context, Item item) throws Exception { List bundles = getIIIFBundles(item); boolean done = false; for (Bundle bundle : bundles) { - List myBitstreams = bundle.getBitstreams(); - for (Bitstream myBitstream : myBitstreams) { - done |= processBitstream(context, myBitstream); + List bitstreams = bundle.getBitstreams(); + for (Bitstream bit : bitstreams) { + done |= processBitstream(context, bit); } } + itemService.update(context, item); return done; } @@ -197,15 +207,23 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { */ private boolean processBitstream(Context context, Bitstream bitstream) throws Exception { boolean processed = false; + boolean isUnsupported = bitstream.getFormat(context).getMIMEType().contains("image/jp2"); boolean isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); if (isImage) { - Optional op = bitstream.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') - .contentEquals(IIIF_WIDTH_METADATA)).findFirst(); + Optional op = bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.') + .contentEquals(IIIF_WIDTH_METADATA)).findFirst(); if (op.isEmpty() || forceProcessing) { - InputStream srcStream = bitstreamService.retrieve(context, bitstream); - int[] dims = ImageDimensionReader.getImageDimensions(srcStream); + int[] dims; + if (isUnsupported) { + dims = iiifApiQuery.getImageDimensions(bitstream); + } else { + InputStream stream = bitstreamService.retrieve(context, bitstream); + dims = ImageDimensionReader.getImageDimensions(stream); + } if (dims != null) { processed = setBitstreamMetadata(context, bitstream, dims); + bitstreamService.update(context, bitstream); } } } @@ -216,14 +234,12 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { try { dSpaceObjectService.clearMetadata(context, bitstream, "iiif", "image", "width", Item.ANY); - dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", - "image", "width", null, - String.valueOf(dims[0]), null, -1, -1); + dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", + "image", "width", Item.ANY, String.valueOf(dims[0])); dSpaceObjectService.clearMetadata(context, bitstream, "iiif", "image", "height", Item.ANY); - dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", - "image", "height", null, - String.valueOf(dims[1]), null, -1, -1); + dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", + "image", "height", Item.ANY, String.valueOf(dims[1])); return true; } catch (SQLException e) { System.out.println("Unable to update metadata: " + e.getMessage()); diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java index bb7e2389c2..f8651d8116 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java @@ -18,8 +18,8 @@ public class ImageDimensionReader { public static int[] getImageDimensions(InputStream image) throws Exception { int[] dims = new int[2]; BufferedImage buf = ImageIO.read(image); - int width = buf.getWidth(); - int height = buf.getHeight(); + int width = buf.getWidth(null); + int height = buf.getHeight(null); if (width > 0 && height > 0) { dims[0] = width; dims[1] = height; diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java new file mode 100644 index 0000000000..6cdac16cf0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java @@ -0,0 +1,15 @@ +package org.dspace.app.canvasdimension.factory; + +import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; +import org.dspace.services.factory.DSpaceServicesFactory; + +public abstract class IIIFCanvasDimensionServiceFactory { + + public static IIIFCanvasDimensionServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("iiifCanvasDimensionServiceFactory", + IIIFCanvasDimensionServiceFactory.class); + } + + public abstract IIIFCanvasDimensionService getIiifCanvasDimensionService(); +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java new file mode 100644 index 0000000000..19f31f145b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java @@ -0,0 +1,15 @@ +package org.dspace.app.canvasdimension.factory; + +import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; +import org.springframework.beans.factory.annotation.Autowired; + +public class IIIFCanvasDimensionServiceFactoryImpl extends IIIFCanvasDimensionServiceFactory { + + @Autowired(required = true) + private IIIFCanvasDimensionService iiifCanvasDimensionService; + + @Override + public IIIFCanvasDimensionService getIiifCanvasDimensionService() { + return iiifCanvasDimensionService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java new file mode 100644 index 0000000000..87277905c4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java @@ -0,0 +1,9 @@ +package org.dspace.app.canvasdimension.service; + +import org.dspace.content.Bitstream; + +public interface IIIFApiQueryService { + + public int[] getImageDimensions(Bitstream bitstream); + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java new file mode 100644 index 0000000000..518634edd6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java @@ -0,0 +1,30 @@ +package org.dspace.app.canvasdimension.service; + +import java.util.List; + +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; + +public interface IIIFCanvasDimensionService { + + public void processSite(Context context) throws Exception; + + public void processCommunity(Context context, Community community) throws Exception; + + public void processCollection(Context context, Collection collection) throws Exception; + + public void processItem(Context context, Item item) throws Exception; + + public void setForceProcessing(boolean force); + + public void setIsQuiet(boolean quiet); + + public void setMax2Process(int max2Process); + + public void setSkipList(List skipList); + + + +} diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index d92e0e5880..7e26e6a3af 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -366,4 +366,11 @@ org.dspace.statistics.AnonymizeStatistics + + canvas-dimensions + Add canvas width and height metadata for IIIF enabled items. + + org.dspace.app.canvasdimension.CanvasDimensionCLI + + diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index 2712ad21d0..e8f5a9b164 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -10,6 +10,7 @@ + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 413d0b814a..886929f026 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -17,6 +17,9 @@ + + + From 2b671158bea60d9c5443eb38e708e649c63d1b6a Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 13 Dec 2021 13:34:05 +0100 Subject: [PATCH 0537/1254] 85542: Add filter-media to test scripts --- .../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 36e425ba6b..69524e4f14 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 @@ -40,6 +40,11 @@ + + + + + From 8fef43a782762cd152a3ff5e7c1089b68157b0a3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 13 Dec 2021 14:46:03 +0100 Subject: [PATCH 0538/1254] 85542: Fixes to script repository ITs --- .../dspace/app/rest/ScriptRestRepositoryIT.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 d15265b8ab..db85f18844 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 @@ -96,7 +96,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { ScriptMatcher.matchScript(scriptConfigurations.get(6).getName(), scriptConfigurations.get(6).getDescription()), ScriptMatcher.matchScript(scriptConfigurations.get(7).getName(), - scriptConfigurations.get(7).getDescription()) + scriptConfigurations.get(7).getDescription()), + ScriptMatcher.matchScript(scriptConfigurations.get(8).getName(), + scriptConfigurations.get(8).getDescription()) ))); } @@ -137,11 +139,11 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=7"), Matchers.containsString("size=1")))) + Matchers.containsString("page=8"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(8))) - .andExpect(jsonPath("$.page.totalElements", is(8))); + .andExpect(jsonPath("$.page.totalPages", is(9))) + .andExpect(jsonPath("$.page.totalElements", is(9))); getClient(token).perform(get("/api/system/scripts").param("size", "1").param("page", "1")) @@ -168,11 +170,11 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { Matchers.containsString("page=2"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/system/scripts?"), - Matchers.containsString("page=7"), Matchers.containsString("size=1")))) + Matchers.containsString("page=8"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) .andExpect(jsonPath("$.page.number", is(1))) - .andExpect(jsonPath("$.page.totalPages", is(8))) - .andExpect(jsonPath("$.page.totalElements", is(8))); + .andExpect(jsonPath("$.page.totalPages", is(9))) + .andExpect(jsonPath("$.page.totalElements", is(9))); } @Test From 28f6b310cce36cf538bb70939c22a9c351adeee3 Mon Sep 17 00:00:00 2001 From: Joost Date: Mon, 13 Dec 2021 21:52:51 +0100 Subject: [PATCH 0539/1254] [task 85840] trying to link a test to a required input field --- .../dspaceFolder/config/item-submission.xml | 10 +++++++ .../dspaceFolder/config/submission-forms.xml | 16 ++++++++++++ .../rest/WorkspaceItemRestRepositoryIT.java | 26 +++++++++++++++++++ dspace/config/submission-forms.xml | 2 +- 4 files changed, 53 insertions(+), 1 deletion(-) 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 07aa36fb2b..cd767c3be2 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -20,6 +20,7 @@ + @@ -75,6 +76,11 @@ submission + + org.dspace.app.rest.submit.step.DescribeStep + + + ossrh https://oss.sonatype.org/ + + true false + + 10 From aaffe5ca80f94e5eace3580e02912d1c005048c4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Dec 2021 22:32:58 +0100 Subject: [PATCH 0541/1254] implemented SubmissionAccessOptions endpoint --- .../model/AccessConditionConfiguration.java | 44 ++++++++++ .../AccessConditionConfigurationService.java | 32 ++++++++ .../SubmissionAccessOptionConverter.java | 38 +++++++++ ...ditionDTO.java => AccessConditionDTO.java} | 0 .../model/SubmissionAccessOptionRest.java | 80 +++++++++++++++++++ .../SubmissionAccessOptionResource.java | 27 +++++++ .../SubmissionAccessOptionRestRepository.java | 51 ++++++++++++ 7 files changed, 272 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfigurationService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{UploadBitstreamAccessConditionDTO.java => AccessConditionDTO.java} (100%) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionAccessOptionResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfiguration.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfiguration.java new file mode 100644 index 0000000000..f3c1ef13e1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfiguration.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.submit.model; +import java.util.List; + +/** +* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) +*/ +public class AccessConditionConfiguration { + + private String name; + private Boolean discoverable; + private List options; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getDiscoverable() { + return discoverable; + } + + public void setDiscoverable(Boolean discoverable) { + this.discoverable = discoverable; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfigurationService.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfigurationService.java new file mode 100644 index 0000000000..f517079aa3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfigurationService.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.submit.model; +import java.util.Map; + +/** + * Simple bean to manage different Access Condition configurations + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class AccessConditionConfigurationService { + + /** + * Mapping the submission step process identifier with the configuration + * (see configuration at access-conditions.xml) + */ + private Map map; + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java new file mode 100644 index 0000000000..994e9eb5e6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; +import org.dspace.app.rest.model.SubmissionAccessOptionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.submit.model.AccessConditionConfiguration; +import org.springframework.stereotype.Component; + +/** + * This converter will convert an object of {@Link AccessConditionConfiguration} + * to an object of {@link SubmissionAccessOptionRest}. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +@Component +public class SubmissionAccessOptionConverter + implements DSpaceConverter { + + @Override + public SubmissionAccessOptionRest convert(AccessConditionConfiguration obj, Projection projection) { + SubmissionAccessOptionRest model = new SubmissionAccessOptionRest(); + model.setId(obj.getName()); + model.setDiscoverable(obj.getDiscoverable()); + model.setAccessConditionOptions(obj.getOptions()); + return model; + } + + @Override + public Class getModelClass() { + return AccessConditionConfiguration.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UploadBitstreamAccessConditionDTO.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessConditionDTO.java similarity index 100% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/UploadBitstreamAccessConditionDTO.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessConditionDTO.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java new file mode 100644 index 0000000000..209f7a6c8f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.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.model; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; +import org.dspace.submit.model.AccessConditionOption; + +/** + * The Access Condition Section Configuration REST Resource + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class SubmissionAccessOptionRest extends BaseObjectRest { + + private static final long serialVersionUID = -7708437586052984082L; + + public static final String NAME = "submissionaccessoption"; + public static final String PLURAL = "submissionaccessoptions"; + public static final String CATEGORY = RestAddressableModel.CONFIGURATION; + + private String id; + + private Boolean discoverable; + + private List accessConditionOptions; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Boolean getDiscoverable() { + return discoverable; + } + + public void setDiscoverable(Boolean discoverable) { + this.discoverable = discoverable; + } + + public List getAccessConditionOptions() { + if (Objects.isNull(accessConditionOptions)) { + accessConditionOptions = new ArrayList<>(); + } + return accessConditionOptions; + } + + public void setAccessConditionOptions(List accessConditionOptions) { + this.accessConditionOptions = accessConditionOptions; + } + + @Override + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @JsonIgnore + @SuppressWarnings("rawtypes") + public Class getController() { + return RestResourceController.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionAccessOptionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionAccessOptionResource.java new file mode 100644 index 0000000000..be184774bc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionAccessOptionResource.java @@ -0,0 +1,27 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; +import org.dspace.app.rest.model.SubmissionAccessOptionRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * SubmissionAccessOption HAL Resource. + * This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable. + * + * Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +@RelNameDSpaceResource(SubmissionAccessOptionRest.NAME) +public class SubmissionAccessOptionResource extends DSpaceResource { + + public SubmissionAccessOptionResource(SubmissionAccessOptionRest data, Utils utils) { + super(data, utils); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java new file mode 100644 index 0000000000..a30331fe1f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.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 java.util.Objects; + +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.SubmissionAccessOptionRest; +import org.dspace.core.Context; +import org.dspace.submit.model.AccessConditionConfiguration; +import org.dspace.submit.model.AccessConditionConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible to manage AccessCondition section + * during the submission. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +@Component(SubmissionAccessOptionRest.CATEGORY + "." + SubmissionAccessOptionRest.NAME) +public class SubmissionAccessOptionRestRepository extends DSpaceRestRepository { + + @Autowired + private AccessConditionConfigurationService accessConditionConfigurationService; + + @Override + @PreAuthorize("permitAll()") + public SubmissionAccessOptionRest findOne(Context context, String id) { + AccessConditionConfiguration configuration = accessConditionConfigurationService.getMap().get(id); + return Objects.nonNull(configuration) ? converter.toRest(configuration, utils.obtainProjection()) : null; + } + + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(SubmissionAccessOptionRest.NAME, "findAll"); + } + + @Override + public Class getDomainClass() { + return SubmissionAccessOptionRest.class; + } + +} \ No newline at end of file From c4bbd2e6e017099c336f1f569e67f2e2387662d8 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Dec 2021 22:34:55 +0100 Subject: [PATCH 0542/1254] added tests for SubmissionAccessOptions endpoint --- .../config/spring/api/access-conditions.xml | 32 +++++++ ...ubmissionAccessOptionRestRepositoryIT.java | 90 +++++++++++++++++++ .../matcher/AccessConditionOptionMatcher.java | 37 ++++++++ 3 files changed, 159 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AccessConditionOptionMatcher.java diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml index d65167a562..1f500f6a36 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml @@ -60,4 +60,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java new file mode 100644 index 0000000000..57af4c01c5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java @@ -0,0 +1,90 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.UUID; + +import org.dspace.app.rest.matcher.AccessConditionOptionMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Integration test class for the submissionAccessOptions endpoint. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class SubmissionAccessOptionRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Test + public void findAllTest() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/config/submissionaccessoptions")) + .andExpect(status().isMethodNotAllowed()); + } + + @Test + public void findOneTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/config/submissionaccessoptions/defaultAC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("defaultAC"))) + .andExpect(jsonPath("$.discoverable", is(true))) + .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( + AccessConditionOptionMatcher.matchAccessConditionOption( + "openaccess","Anonymous", false , false, null, null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "embargo","Anonymous", true , false, "+36MONTHS", null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "administrator","Administrator", false , false, null, null)) + )) + .andExpect(jsonPath("$.type", is("submissionaccessoption"))); + + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/config/submissionaccessoptions/defaultAC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("defaultAC"))) + .andExpect(jsonPath("$.discoverable", is(true))) + .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( + AccessConditionOptionMatcher.matchAccessConditionOption( + "openaccess","Anonymous", false , false, null, null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "embargo","Anonymous", true , false, "+36MONTHS", null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "administrator","Administrator", false , false, null, null)) + )) + .andExpect(jsonPath("$.type", is("submissionaccessoption"))); + + getClient().perform(get("/api/config/submissionaccessoptions/defaultAC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("defaultAC"))) + .andExpect(jsonPath("$.discoverable", is(true))) + .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( + AccessConditionOptionMatcher.matchAccessConditionOption( + "openaccess","Anonymous", false , false, null, null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "embargo","Anonymous", true , false, "+36MONTHS", null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "administrator","Administrator", false , false, null, null)) + )) + .andExpect(jsonPath("$.type", is("submissionaccessoption"))); + } + + @Test + public void findOneNotFoundTest() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/config/submissionaccessoptions/" + UUID.randomUUID())) + .andExpect(status().isNotFound()); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AccessConditionOptionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AccessConditionOptionMatcher.java new file mode 100644 index 0000000000..e28a2e224d --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AccessConditionOptionMatcher.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.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.hamcrest.Matcher; + +/** + * Provide convenient org.hamcrest.Matcher to verify + * a SubmissionAccessOptionRest json response. + * + * @author Mykhaylo Boychuk (mykhaylob.oychuk at 4science.com) + */ +public class AccessConditionOptionMatcher { + + private AccessConditionOptionMatcher() {} + + public static Matcher matchAccessConditionOption(String name, String groupName, + boolean hasStartDate, boolean hasEndDate, String startDateLimit, String endDateLimit) { + return allOf( + hasJsonPath("$.name", is(name)), + hasJsonPath("$.groupName", is(groupName)), + hasJsonPath("$.hasStartDate", is(hasStartDate)), + hasJsonPath("$.hasEndDate", is(hasEndDate)), + hasJsonPath("$.startDateLimit", is(startDateLimit)), + hasJsonPath("$.endDateLimit", is(endDateLimit)) + ); + } +} \ No newline at end of file From 85c7252ab95a9318dfe2b261791ef3594ef8059e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 13 Dec 2021 16:35:40 -0600 Subject: [PATCH 0543/1254] Update dependencies in Docker. --- Dockerfile | 10 +++++----- Dockerfile.cli | 4 ++-- Dockerfile.test | 10 +++++----- docker-compose.yml | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2dc3ee9bda..f1b919ea87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,8 @@ # See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # # This version is JDK11 compatible -# - tomcat:8-jdk11 -# - ANT 1.10.7 +# - tomcat:9-jdk11 +# - ANT 1.10.12 # - maven:3-jdk-11 (see dspace-dependencies) # - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x @@ -30,13 +30,13 @@ RUN mvn package && \ mvn clean # Step 2 - Run Ant Deploy -FROM tomcat:8-jdk11 as ant_build +FROM tomcat:9-jdk11 as ant_build ARG TARGET_DIR=dspace-installer COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.7 +ENV ANT_VERSION 1.10.12 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH @@ -47,7 +47,7 @@ RUN ant init_installation update_configs update_code update_webapps # Step 3 - Run tomcat # Create a new tomcat image that does not retain the the build directory contents -FROM tomcat:8-jdk11 +FROM tomcat:9-jdk11 ENV DSPACE_INSTALL=/dspace COPY --from=ant_build /dspace $DSPACE_INSTALL EXPOSE 8080 8009 diff --git a/Dockerfile.cli b/Dockerfile.cli index d4204ebdd0..0ca19b97d6 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -3,7 +3,7 @@ # # This version is JDK11 compatible # - openjdk:11 -# - ANT 1.10.7 +# - ANT 1.10.12 # - maven:3-jdk-11 (see dspace-dependencies) # - note: default tag for branch: dspace/dspace-cli: dspace/dspace-cli:dspace-7_x @@ -35,7 +35,7 @@ COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.7 +ENV ANT_VERSION 1.10.12 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH diff --git a/Dockerfile.test b/Dockerfile.test index f8d124b3ae..65eee9e9f1 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -2,8 +2,8 @@ # See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # # This version is JDK11 compatible -# - tomcat:8-jdk11 -# - ANT 1.10.7 +# - tomcat:9-jdk11 +# - ANT 1.10.12 # - maven:3-jdk-11 (see dspace-dependencies) # - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-test # @@ -32,13 +32,13 @@ RUN mvn package -Pdspace-rest && \ mvn clean # Step 2 - Run Ant Deploy -FROM tomcat:8-jdk11 as ant_build +FROM tomcat:9-jdk11 as ant_build ARG TARGET_DIR=dspace-installer COPY --from=build /install /dspace-src WORKDIR /dspace-src # Create the initial install deployment using ANT -ENV ANT_VERSION 1.10.7 +ENV ANT_VERSION 1.10.12 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH @@ -49,7 +49,7 @@ RUN ant init_installation update_configs update_code update_webapps # Step 3 - Run tomcat # Create a new tomcat image that does not retain the the build directory contents -FROM tomcat:8-jdk11 +FROM tomcat:9-jdk11 ENV DSPACE_INSTALL=/dspace ENV TOMCAT_INSTALL=/usr/local/tomcat COPY --from=ant_build /dspace $DSPACE_INSTALL diff --git a/docker-compose.yml b/docker-compose.yml index 64dca2e4ce..9e2583ecad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: dspacesolr: container_name: dspacesolr # Uses official Solr image at https://hub.docker.com/_/solr/ - image: solr:8.8 + image: solr:8.11-slim networks: dspacenet: ports: From f919986bdb5c8c36e8b644b2c236b33a220a38d4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Dec 2021 23:54:15 +0100 Subject: [PATCH 0544/1254] implemented remove patch operation for workspaceitem-data-access endpoint --- .../dao/impl/ResourcePolicyDAOImpl.java | 5 ++ .../submit/model/AccessConditionOption.java | 64 +++++++++++-- .../app/rest/model/AccessConditionDTO.java | 2 +- .../rest/model/step/DataAccessCondition.java | 55 ++++++++++++ .../rest/model/step/UploadBitstreamRest.java | 11 ++- .../app/rest/submit/DataProcessingStep.java | 2 + .../app/rest/submit/SubmissionService.java | 10 +-- ...tionDiscoverableReplacePatchOperation.java | 77 ++++++++++++++++ .../AccessConditionRemovePatchOperation.java | 80 +++++++++++++++++ ...streamResourcePolicyAddPatchOperation.java | 16 ++-- ...eamResourcePolicyRemovePatchOperation.java | 12 +-- .../rest/submit/step/AccessConditionStep.java | 90 +++++++++++++++++++ .../app/rest/submit/step/UploadStep.java | 4 +- .../spring/spring-dspace-core-services.xml | 16 +++- 14 files changed, 409 insertions(+), 35 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataAccessCondition.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/AccessConditionStep.java diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java index 9dd368d667..651c1ad63b 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java @@ -8,12 +8,14 @@ package org.dspace.authorize.dao.impl; import java.sql.SQLException; +import java.util.LinkedList; import java.util.List; import java.util.UUID; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Root; import org.dspace.authorize.ResourcePolicy; @@ -60,6 +62,9 @@ public class ResourcePolicyDAOImpl extends AbstractHibernateDAO criteriaBuilder.equal(resourcePolicyRoot.get(ResourcePolicy_.rptype), type) ) ); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(resourcePolicyRoot.get(ResourcePolicy_.id))); + criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1); } diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java index b9e9f84438..1c981a8b09 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java @@ -7,14 +7,18 @@ */ package org.dspace.submit.model; +import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; +import static org.dspace.core.Constants.READ; + import java.sql.SQLException; import java.text.ParseException; import java.util.Date; +import java.util.Objects; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.Bitstream; +import org.dspace.content.DSpaceObject; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; @@ -134,9 +138,9 @@ public class AccessConditionOption { } /** - * Create a new resource policy for a bitstream + * Create a new resource policy for a DSpaceObject * @param context DSpace context - * @param b bitstream for which resource policy is created + * @param obj DSpaceObject for which resource policy is created * @param name name of the resource policy * @param description description of the resource policy * @param startDate start date of the resource policy. If {@link #getHasStartDate()} returns false, @@ -144,7 +148,7 @@ public class AccessConditionOption { * @param endDate end date of the resource policy. If {@link #getHasEndDate()} returns false, * endDate should be null. Otherwise endDate may not be null. */ - public void createResourcePolicy(Context context, Bitstream b, String name, String description, + public void createResourcePolicy(Context context, DSpaceObject obj, String name, String description, Date startDate, Date endDate) throws SQLException, AuthorizeException, ParseException { if (getHasStartDate() && startDate == null) { @@ -187,8 +191,58 @@ public class AccessConditionOption { } Group group = groupService.findByName(context, getGroupName()); - authorizeService.createResourcePolicy(context, b, group, null, Constants.READ, + authorizeService.createResourcePolicy(context, obj, group, null, Constants.READ, ResourcePolicy.TYPE_CUSTOM, name, description, startDate, endDate); } + + public boolean canCreateResourcePolicy(Context context, String name, Date startDate, Date endDate) + throws SQLException, AuthorizeException, ParseException { + if (getHasStartDate() && Objects.isNull(startDate)) { + throw new IllegalStateException("The access condition " + getName() + " requires a start date."); + } + if (getHasEndDate() && Objects.isNull(endDate)) { + throw new IllegalStateException("The access condition " + getName() + " requires an end date."); + } + if (!getHasStartDate() && Objects.nonNull(startDate)) { + throw new IllegalStateException("The access condition " + getName() + " cannot contain a start date."); + } + if (!getHasEndDate() && Objects.nonNull(endDate)) { + throw new IllegalStateException("The access condition " + getName() + " cannot contain an end date."); + } + + Date latestStartDate = null; + if (Objects.nonNull(getStartDateLimit())) { + latestStartDate = dateMathParser.parseMath(getStartDateLimit()); + } + + Date latestEndDate = null; + if (Objects.nonNull(getEndDateLimit())) { + latestEndDate = dateMathParser.parseMath(getEndDateLimit()); + } + + // throw if startDate after latestStartDate + if (Objects.nonNull(startDate) && Objects.nonNull(latestStartDate) && startDate.after(latestStartDate)) { + throw new IllegalStateException(String.format( + "The start date of access condition %s should be earlier than %s from now.", + getName(), getStartDateLimit() + )); + } + + // throw if endDate after latestEndDate + if (Objects.nonNull(endDate) && Objects.nonNull(latestEndDate) && endDate.after(latestEndDate)) { + throw new IllegalStateException(String.format( + "The end date of access condition %s should be earlier than %s from now.", + getName(), getEndDateLimit() + )); + } + return true; + } + + public ResourcePolicy createPolicy(Context context, DSpaceObject obj, String name, String description, + Date startDate, Date endDate) throws SQLException, AuthorizeException { + Group group = groupService.findByName(context, getGroupName()); + return authorizeService.createResourcePolicy(context, obj, group, null, READ, TYPE_CUSTOM, + name, description, startDate, endDate); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessConditionDTO.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessConditionDTO.java index 7ede7a6f8f..f5302de1a9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessConditionDTO.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessConditionDTO.java @@ -25,7 +25,7 @@ import org.dspace.app.rest.model.step.UploadBitstreamRest; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) */ -public class UploadBitstreamAccessConditionDTO { +public class AccessConditionDTO { private Integer id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataAccessCondition.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataAccessCondition.java new file mode 100644 index 0000000000..1ad708c8a0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataAccessCondition.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.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.dspace.app.rest.model.AccessConditionDTO; + +/** + * Java Bean to expose the access conditions during in progress submission. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class DataAccessCondition implements SectionData { + + private static final long serialVersionUID = -502652293641527738L; + + /** + * If discoverable = 'false' indicates whether the current item hidden + * from all search/browse/OAI results, and is therefore only + * accessible via direct link (or bookmark). + */ + private Boolean discoverable; + + /** + * An array of all the policies that has been applied by the user to the item. + */ + private List accessConditions; + + public List getAccessConditions() { + if (Objects.isNull(accessConditions)) { + accessConditions = new ArrayList<>(); + } + return accessConditions; + } + + public void setAccessConditions(List accessConditions) { + this.accessConditions = accessConditions; + } + + public Boolean getDiscoverable() { + return discoverable; + } + + public void setDiscoverable(Boolean discoverable) { + this.discoverable = discoverable; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/UploadBitstreamRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/UploadBitstreamRest.java index 58e8fc157c..70439d4248 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/UploadBitstreamRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/UploadBitstreamRest.java @@ -6,21 +6,20 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.model.step; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.app.rest.model.BitstreamFormatRest; import org.dspace.app.rest.model.CheckSumRest; import org.dspace.app.rest.model.MetadataValueRest; -import org.dspace.app.rest.model.UploadBitstreamAccessConditionDTO; /** * This Java Bean is used to represent a single bitstream with all its metadata - * and access conditions ({@link UploadBitstreamAccessConditionDTO}) inside an + * and access conditions ({@link AccessConditionDTO}) inside an * upload submission section ({@link DataUpload} * */ @@ -28,7 +27,7 @@ public class UploadBitstreamRest extends UploadStatusResponse { private UUID uuid; private Map> metadata = new HashMap<>(); - private List accessConditions; + private List accessConditions; private BitstreamFormatRest format; private Long sizeBytes; private CheckSumRest checkSum; @@ -74,14 +73,14 @@ public class UploadBitstreamRest extends UploadStatusResponse { this.metadata = metadata; } - public List getAccessConditions() { + public List getAccessConditions() { if (accessConditions == null) { accessConditions = new ArrayList<>(); } return accessConditions; } - public void setAccessConditions(List accessConditions) { + public void setAccessConditions(List accessConditions) { this.accessConditions = accessConditions; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java index 15a87e07b9..8eb03acde0 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 @@ -36,6 +36,8 @@ public interface DataProcessingStep extends RestProcessingStep { public static final String UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY = "accessConditions"; public static final String LICENSE_STEP_OPERATION_ENTRY = "granted"; 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 UPLOAD_STEP_METADATA_PATH = "metadata"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java index 227925357d..8e72008c79 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java @@ -22,11 +22,11 @@ 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.AInprogressSubmissionRest; +import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CheckSumRest; import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.MetadataValueRest; -import org.dspace.app.rest.model.UploadBitstreamAccessConditionDTO; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.step.DataCCLicense; @@ -162,7 +162,7 @@ public class SubmissionService { /** * Build the rest representation of a bitstream as used in the upload section * ({@link DataUpload}. It contains all its metadata and the list of applied - * access conditions (@link {@link UploadBitstreamAccessConditionDTO} + * access conditions (@link {@link AccessConditionDTO} * * @param configurationService the DSpace ConfigurationService * @param source the bitstream to translate in its rest submission @@ -204,7 +204,7 @@ public class SubmissionService { for (ResourcePolicy rp : source.getResourcePolicies()) { if (ResourcePolicy.TYPE_CUSTOM.equals(rp.getRpType())) { - UploadBitstreamAccessConditionDTO uploadAccessCondition = createAccessConditionFromResourcePolicy(rp); + AccessConditionDTO uploadAccessCondition = createAccessConditionFromResourcePolicy(rp); data.getAccessConditions().add(uploadAccessCondition); } } @@ -271,8 +271,8 @@ public class SubmissionService { return wi; } - private UploadBitstreamAccessConditionDTO createAccessConditionFromResourcePolicy(ResourcePolicy rp) { - UploadBitstreamAccessConditionDTO accessCondition = new UploadBitstreamAccessConditionDTO(); + private AccessConditionDTO createAccessConditionFromResourcePolicy(ResourcePolicy rp) { + AccessConditionDTO accessCondition = new AccessConditionDTO(); accessCondition.setId(rp.getID()); accessCondition.setName(rp.getRpName()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java new file mode 100644 index 0000000000..3577ec977c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java @@ -0,0 +1,77 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.factory.impl; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.BooleanUtils; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.submit.model.AccessConditionConfiguration; +import org.dspace.submit.model.AccessConditionConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "add" PATCH operation + * to change item discoverable true|false. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class AccessConditionDiscoverableReplacePatchOperation extends ReplacePatchOperation { + + @Autowired + private AccessConditionConfigurationService accessConditionConfigurationService; + + @Override + void replace(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String string, + Object value) throws Exception { + Boolean discoverable = null; + + String stepId = (String) currentRequest.getAttribute("accessConditionSectionId"); + AccessConditionConfiguration configuration = accessConditionConfigurationService.getMap().get(stepId); + + if (Objects.isNull(configuration) || !configuration.getDiscoverable().booleanValue()) { + throw new UnprocessableEntityException("The current access configurations does not allow" + + " the user to specify the visibility of the item"); + } + + if (value instanceof String) { + discoverable = BooleanUtils.toBooleanObject((String) value); + } else { + discoverable = (Boolean) value; + } + + if (Objects.isNull(discoverable)) { + throw new UnprocessableEntityException( + "Value is not a valid boolean expression permitted value: true|false"); + } + + Item item = source.getItem(); + + if (discoverable == item.isDiscoverable()) { + return; + } else if (discoverable) { + item.setDiscoverable(discoverable); + } else { + item.setDiscoverable(discoverable); + } + } + + @Override + protected Class getArrayClassForEvaluation() { + return String[].class; + } + + @Override + protected Class getClassForEvaluation() { + return String.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java new file mode 100644 index 0000000000..47fd4c384a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.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.submit.factory.impl; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.AccessConditionDTO; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "remove" operation to remove custom resource policies. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class AccessConditionRemovePatchOperation extends RemovePatchOperation { + + @Autowired + private AuthorizeService authorizeService; + @Autowired + private ResourcePolicyService resourcePolicyService; + + @Override + void remove(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, + Object value) throws Exception { + + // "path" : "/sections/<:name-of-the-form>/accessConditions/0" + // "abspath" : "/accessConditions/0" + String[] split = getAbsolutePath(path).split("/"); + Item item = source.getItem(); + + if (split.length == 1) { + // reset the access condition to the empty array + authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + } else if (split.length == 2) { + // to remove an access condition + // contains "<:access-idx>" + Integer idxToDelete = null; + try { + idxToDelete = Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + throw new UnprocessableEntityException("The provided index format is not correct! Must be a number!"); + } + + List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); + if ((idxToDelete < 0 || idxToDelete >= policies.size()) && policies.isEmpty()) { + throw new UnprocessableEntityException("The provided index:" + idxToDelete + " is not supported," + + " currently the are " + policies.size() + " access conditions"); + } + + ResourcePolicy resourcePolicyToDelete = policies.get(idxToDelete); + item.getResourcePolicies().remove(resourcePolicyToDelete); + context.commit(); + resourcePolicyService.delete(context, resourcePolicyToDelete); + } + } + + @Override + protected Class getArrayClassForEvaluation() { + return AccessConditionDTO[].class; + } + + @Override + protected Class getClassForEvaluation() { + return AccessConditionDTO.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyAddPatchOperation.java index 144ff438ee..66d2440a40 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyAddPatchOperation.java @@ -14,7 +14,7 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.commons.collections.CollectionUtils; -import org.dspace.app.rest.model.UploadBitstreamAccessConditionDTO; +import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.app.rest.model.patch.LateObjectEvaluator; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; @@ -33,7 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -public class BitstreamResourcePolicyAddPatchOperation extends AddPatchOperation { +public class BitstreamResourcePolicyAddPatchOperation extends AddPatchOperation { @Autowired @@ -61,8 +61,8 @@ public class BitstreamResourcePolicyAddPatchOperation extends AddPatchOperation< for (Bitstream bitstream : bb.getBitstreams()) { if (idx == Integer.parseInt(split[1])) { - List newAccessConditions = - new ArrayList(); + List newAccessConditions = + new ArrayList(); if (split.length == 3) { authorizeService.removePoliciesActionFilter(context, bitstream, Constants.READ); newAccessConditions = evaluateArrayObject((LateObjectEvaluator) value); @@ -83,12 +83,12 @@ public class BitstreamResourcePolicyAddPatchOperation extends AddPatchOperation< } @Override - protected Class getArrayClassForEvaluation() { - return UploadBitstreamAccessConditionDTO[].class; + protected Class getArrayClassForEvaluation() { + return AccessConditionDTO[].class; } @Override - protected Class getClassForEvaluation() { - return UploadBitstreamAccessConditionDTO.class; + protected Class getClassForEvaluation() { + return AccessConditionDTO.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java index d0d805f0bf..edb606b214 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyRemovePatchOperation.java @@ -10,7 +10,7 @@ package org.dspace.app.rest.submit.factory.impl; import java.util.List; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.rest.model.UploadBitstreamAccessConditionDTO; +import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; @@ -29,7 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ public class BitstreamResourcePolicyRemovePatchOperation - extends RemovePatchOperation { + extends RemovePatchOperation { @Autowired ItemService itemService; @@ -84,12 +84,12 @@ public class BitstreamResourcePolicyRemovePatchOperation } @Override - protected Class getArrayClassForEvaluation() { - return UploadBitstreamAccessConditionDTO[].class; + protected Class getArrayClassForEvaluation() { + return AccessConditionDTO[].class; } @Override - protected Class getClassForEvaluation() { - return UploadBitstreamAccessConditionDTO.class; + protected Class getClassForEvaluation() { + return AccessConditionDTO.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/AccessConditionStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/AccessConditionStep.java new file mode 100644 index 0000000000..070e4a3cbf --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/AccessConditionStep.java @@ -0,0 +1,90 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.submit.step; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.AccessConditionDTO; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.step.DataAccessCondition; +import org.dspace.app.rest.submit.AbstractProcessingStep; +import org.dspace.app.rest.submit.SubmissionService; +import org.dspace.app.rest.submit.factory.PatchOperationFactory; +import org.dspace.app.rest.submit.factory.impl.PatchOperation; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * AccessCondition step for DSpace Spring Rest. Expose information about + * the resource policies for the in progress submission. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class AccessConditionStep extends AbstractProcessingStep { + + @Override + @SuppressWarnings("unchecked") + public DataAccessCondition getData(SubmissionService submissionService, InProgressSubmission obj, + SubmissionStepConfig config) throws Exception { + DataAccessCondition accessCondition = new DataAccessCondition(); + accessCondition.setDiscoverable(obj.getItem().isDiscoverable()); + accessCondition.setAccessConditions(getAccessConditionList(obj.getItem())); + return accessCondition; + } + + @Override + @SuppressWarnings("unchecked") + public void doPatchProcessing(Context context, HttpServletRequest currentRequest, InProgressSubmission source, + Operation op, SubmissionStepConfig stepConf) throws Exception { + String instance = StringUtils.EMPTY; + if (op.getPath().contains(ACCESS_CONDITION_STEP_OPERATION_ENTRY)) { + instance = ACCESS_CONDITION_STEP_OPERATION_ENTRY; + } else if (op.getPath().contains(ACCESS_CONDITION_POLICY_STEP_OPERATION_ENTRY)) { + instance = ACCESS_CONDITION_POLICY_STEP_OPERATION_ENTRY; + } + + if (StringUtils.isBlank(instance)) { + throw new UnprocessableEntityException("The path " + op.getPath() + " is not supported by the operation " + + op.getOp()); + } + + currentRequest.setAttribute("accessConditionSectionId", stepConf.getId()); + PatchOperation patchOperation = new PatchOperationFactory().instanceOf(instance, op.getOp()); + patchOperation.perform(context, currentRequest, source, op); + } + + private List getAccessConditionList(Item item) { + List accessConditions = new ArrayList(); + for (ResourcePolicy rp : item.getResourcePolicies()) { + if (ResourcePolicy.TYPE_CUSTOM.equals(rp.getRpType())) { + AccessConditionDTO accessConditionDTO = createAccessConditionFromResourcePolicy(rp); + accessConditions.add(accessConditionDTO); + } + } + return accessConditions; + } + + private AccessConditionDTO createAccessConditionFromResourcePolicy(ResourcePolicy rp) { + AccessConditionDTO accessCondition = new AccessConditionDTO(); + + accessCondition.setId(rp.getID()); + accessCondition.setName(rp.getRpName()); + accessCondition.setDescription(rp.getRpDescription()); + accessCondition.setStartDate(rp.getStartDate()); + accessCondition.setEndDate(rp.getEndDate()); + return accessCondition; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java index 33d0a02f8c..b91916ca31 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/UploadStep.java @@ -72,7 +72,7 @@ public class UploadStep extends AbstractProcessingStep if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; } else if (op.getPath().contains(UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY)) { - instance = UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; + instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; } else { instance = UPLOAD_STEP_REMOVE_OPERATION_ENTRY; } @@ -84,7 +84,7 @@ public class UploadStep extends AbstractProcessingStep } } else { if (op.getPath().contains(UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY)) { - instance = UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; + instance = stepConf.getType() + "." + UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY; } else if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) { instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY; } diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index ce9b3ab2a2..8de0c3b8dc 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -52,7 +52,7 @@ - + @@ -60,6 +60,9 @@ + + + @@ -91,6 +94,9 @@ + + + @@ -110,7 +116,7 @@ - + @@ -118,6 +124,12 @@ + + + + + + From 420f661a5b3e3880ef5bb5e23fa37cf69940a413 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Dec 2021 09:14:25 +0100 Subject: [PATCH 0545/1254] refactoring --- .../impl/BitstreamResourcePolicyUtils.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyUtils.java index 25794940fd..3aa46fb8c8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/BitstreamResourcePolicyUtils.java @@ -13,14 +13,12 @@ import java.util.Date; import java.util.Iterator; import java.util.List; -import org.dspace.app.rest.model.UploadBitstreamAccessConditionDTO; +import org.dspace.app.rest.model.AccessConditionDTO; import org.dspace.authorize.AuthorizeException; -import org.dspace.content.Bitstream; +import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.submit.model.AccessConditionOption; import org.dspace.submit.model.UploadConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Utility class to reuse methods related to bitstream resource-policies @@ -29,49 +27,47 @@ import org.slf4j.LoggerFactory; */ public class BitstreamResourcePolicyUtils { - private static final Logger log = LoggerFactory.getLogger(BitstreamResourcePolicyUtils.class); - /** * Default constructor */ private BitstreamResourcePolicyUtils() { } /** - * Based on the given access condition, find the resource policy to apply on the given bitstream + * Based on the given access condition, find the resource policy to apply on the given DSpace object * This function applies the resource policies. * * @param context The relevant DSpace Context. * @param uploadConfigs The configured UploadConfigurations - * @param b The applicable bitstream whose policies should be determined + * @param obj The applicable DSpace object whose policies should be determined * @param newAccessCondition The access condition containing the details for the desired policies * @throws SQLException If a database error occurs * @throws AuthorizeException If the user is not authorized */ public static void findApplyResourcePolicy(Context context, Iterator uploadConfigs, - Bitstream b, List newAccessConditions) + DSpaceObject obj, List newAccessConditions) throws SQLException, AuthorizeException, ParseException { while (uploadConfigs.hasNext()) { UploadConfiguration uploadConfiguration = uploadConfigs.next(); - for (UploadBitstreamAccessConditionDTO newAccessCondition : newAccessConditions) { + for (AccessConditionDTO newAccessCondition : newAccessConditions) { String name = newAccessCondition.getName(); String description = newAccessCondition.getDescription(); Date startDate = newAccessCondition.getStartDate(); Date endDate = newAccessCondition.getEndDate(); - findApplyResourcePolicy(context, uploadConfiguration, b, name, description, startDate, endDate); + findApplyResourcePolicy(context, uploadConfiguration, obj, name, description, startDate, endDate); } } } /** - * Based on the given name, find the resource policy to apply on the given bitstream + * Based on the given name, find the resource policy to apply on the given DSpace object * This function applies the resource policies. * The description, start date and end date are applied as well * * @param context The relevant DSpace Context. * @param uploadConfigs The configured UploadConfigurations - * @param b The applicable bitstream whose policies should be determined + * @param obj The applicable DSpace object whose policies should be determined * @param name The name of the access condition matching the desired policies * @param description An optional description for the policies * @param startDate An optional start date for the policies @@ -80,12 +76,12 @@ public class BitstreamResourcePolicyUtils { * @throws AuthorizeException If the user is not authorized */ public static void findApplyResourcePolicy(Context context, UploadConfiguration uploadConfiguration, - Bitstream b, String name, String description, + DSpaceObject obj, String name, String description, Date startDate, Date endDate) throws SQLException, AuthorizeException, ParseException { for (AccessConditionOption aco : uploadConfiguration.getOptions()) { if (aco.getName().equalsIgnoreCase(name)) { - aco.createResourcePolicy(context, b, name, description, startDate, endDate); + aco.createResourcePolicy(context, obj, name, description, startDate, endDate); return; } } From e0af26180f3e359cf8d35a6cc6f30886fc479db2 Mon Sep 17 00:00:00 2001 From: Joost Date: Tue, 14 Dec 2021 10:51:24 +0100 Subject: [PATCH 0546/1254] [task 85840] test for required qualdrops --- .../dspaceFolder/config/item-submission.xml | 6 +-- .../dspaceFolder/config/submission-forms.xml | 2 +- .../rest/WorkspaceItemRestRepositoryIT.java | 39 ++++++++++++++++--- 3 files changed, 38 insertions(+), 9 deletions(-) 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 cd767c3be2..7773135e1c 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -76,9 +76,9 @@ submission - + org.dspace.app.rest.submit.step.DescribeStep - + submission-form + @@ -167,6 +181,12 @@ + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml index 1f500f6a36..a879f1be91 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml @@ -82,7 +82,7 @@ - + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java new file mode 100644 index 0000000000..364098bb24 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java @@ -0,0 +1,105 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.factory.impl; +import java.sql.SQLException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.AccessConditionDTO; +import org.dspace.app.rest.model.patch.LateObjectEvaluator; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.submit.model.AccessConditionConfiguration; +import org.dspace.submit.model.AccessConditionConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "add" operation to add custom resource policies. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class AccessConditionAddPatchOperation extends AddPatchOperation { + + @Autowired + private AuthorizeService authorizeService; + @Autowired + private ResourcePolicyService resourcePolicyService; + @Autowired + private AccessConditionConfigurationService accessConditionConfigurationService; + + @Override + void add(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, Object value) + throws Exception { + + String stepId = (String) currentRequest.getAttribute("accessConditionSectionId"); + AccessConditionConfiguration configuration = accessConditionConfigurationService.getMap().get(stepId); + + //"path": "/sections/<:name-of-the-form>/accessConditions/-" + String[] split = getAbsolutePath(path).split("/"); + Item item = source.getItem(); + + List accessConditions = new ArrayList(); + + if (split.length == 1) { + // to replace completely the access conditions + authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + accessConditions = evaluateArrayObject((LateObjectEvaluator) value); + } else if (split.length == 2) { + // to add an access condition + // contains "-", call index-based accessConditions it make not sense + accessConditions.add(evaluateSingleObject((LateObjectEvaluator) value)); + } + + verifyAccessConditions(context, configuration, accessConditions); + // check duplicate policy + checkDuplication(context, item, accessConditions); + // apply policies + AccessConditionResourcePolicyUtils.findApplyResourcePolicy(context, configuration.getOptions(), item, + accessConditions); + } + + private void verifyAccessConditions(Context context, AccessConditionConfiguration configuration, + List accessConditions) throws SQLException, AuthorizeException, ParseException { + for (AccessConditionDTO dto : accessConditions) { + AccessConditionResourcePolicyUtils.canApplyResourcePolicy(context, configuration.getOptions(), + dto.getName(), dto.getStartDate(), dto.getEndDate()); + } + } + + private void checkDuplication(Context context, Item item, List accessConditions) + throws SQLException { + List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); + for (ResourcePolicy resourcePolicy : policies) { + for (AccessConditionDTO dto : accessConditions) { + if (dto.getName().equals(resourcePolicy.getRpName())) { + throw new UnprocessableEntityException("A policy of the same type already exists!"); + } + } + } + } + + @Override + protected Class getArrayClassForEvaluation() { + return AccessConditionDTO[].class; + } + + @Override + protected Class getClassForEvaluation() { + return AccessConditionDTO.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java index 47fd4c384a..114c082000 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java @@ -55,7 +55,7 @@ public class AccessConditionRemovePatchOperation extends RemovePatchOperation policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); - if ((idxToDelete < 0 || idxToDelete >= policies.size()) && policies.isEmpty()) { + if (idxToDelete < 0 || idxToDelete >= policies.size()) { throw new UnprocessableEntityException("The provided index:" + idxToDelete + " is not supported," + " currently the are " + policies.size() + " access conditions"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java new file mode 100644 index 0000000000..95c33c5770 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java @@ -0,0 +1,178 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.factory.impl; +import java.sql.SQLException; +import java.text.ParseException; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.AccessConditionDTO; +import org.dspace.app.rest.model.patch.JsonValueEvaluator; +import org.dspace.app.rest.model.patch.LateObjectEvaluator; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.submit.model.AccessConditionConfiguration; +import org.dspace.submit.model.AccessConditionConfigurationService; +import org.dspace.submit.model.AccessConditionOption; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission "replace" operation to replace custom resource policies. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class AccessConditionReplacePatchOperation extends ReplacePatchOperation { + + @Autowired + private ResourcePolicyService resourcePolicyService; + @Autowired + private AccessConditionConfigurationService accessConditionConfigurationService; + + @Override + void replace(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, + Object value) throws Exception { + + String stepId = (String) currentRequest.getAttribute("accessConditionSectionId"); + AccessConditionConfiguration configuration = accessConditionConfigurationService.getMap().get(stepId); + + // "path" : "/sections/<:name-of-the-form>/accessConditions/0" + String[] split = getAbsolutePath(path).split("/"); + Item item = source.getItem(); + if (split.length == 2) { + int toReplace = Integer.parseInt(split[1]); + AccessConditionDTO accessConditionDTO = evaluateSingleObject((LateObjectEvaluator) value); + if (Objects.nonNull(accessConditionDTO) && Objects.nonNull(getOption(configuration, accessConditionDTO))) { + List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); + if ((toReplace < 0 || toReplace >= policies.size()) && policies.isEmpty()) { + throw new UnprocessableEntityException("The provided index:" + toReplace + " is not supported," + + " currently the are " + policies.size() + " access conditions"); + } + if (checkDubblication(context, configuration, policies, accessConditionDTO, toReplace, item)) { + context.commit(); + resourcePolicyService.delete(context, policies.get(toReplace)); + getOption(configuration, accessConditionDTO).createPolicy(context, item, + accessConditionDTO.getName(), null, accessConditionDTO.getStartDate(), + accessConditionDTO.getEndDate()); + } + } + } else if (split.length == 3) { + String valueToReplare = getValue(value); + int toReplace = Integer.parseInt(split[1]); + String attributeReplace = split[2]; + List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); + if ((toReplace < 0 || toReplace >= policies.size()) && policies.isEmpty()) { + throw new UnprocessableEntityException("The provided index:" + toReplace + " is not supported," + + " currently the are " + policies.size() + " access conditions"); + } + ResourcePolicy rpToReplace = policies.get(toReplace); + AccessConditionDTO accessConditionDTO = createDTO(rpToReplace, attributeReplace, valueToReplare); + boolean canApplay = AccessConditionResourcePolicyUtils.canApplyResourcePolicy(context, + configuration.getOptions(), accessConditionDTO.getName(), accessConditionDTO.getStartDate(), + accessConditionDTO.getEndDate()); + if (canApplay) { + switch (attributeReplace) { + case "name": + rpToReplace.setRpName(valueToReplare); + break; + case "startDate": + rpToReplace.setStartDate(new Date(valueToReplare)); + break; + case "endDate": + rpToReplace.setEndDate(new Date(valueToReplare)); + break; + default: + } + } + resourcePolicyService.update(context, rpToReplace); + } + } + + private AccessConditionDTO createDTO(ResourcePolicy rpToReplace, String attributeReplace, String valueToReplare) { + AccessConditionDTO accessCondition = new AccessConditionDTO(); + switch (attributeReplace) { + case "name": + accessCondition.setName(valueToReplare); + accessCondition.setStartDate(rpToReplace.getStartDate()); + accessCondition.setEndDate(rpToReplace.getEndDate()); + return accessCondition; + case "startDate": + accessCondition.setName(rpToReplace.getRpName()); + accessCondition.setStartDate(new Date(valueToReplare)); + accessCondition.setEndDate(rpToReplace.getEndDate()); + return accessCondition; + case "endDate": + accessCondition.setName(rpToReplace.getRpName()); + accessCondition.setStartDate(rpToReplace.getStartDate()); + accessCondition.setEndDate(new Date(valueToReplare)); + return accessCondition; + default: + throw new UnprocessableEntityException("The provided attribute: " + + attributeReplace + " is not supported"); + } + } + + private String getValue(Object value) { + if (value instanceof JsonValueEvaluator) { + JsonValueEvaluator jsonValue = (JsonValueEvaluator) value; + if (jsonValue.getValueNode().fields().hasNext()) { + return jsonValue.getValueNode().fields().next().getValue().asText(); + } + } + return StringUtils.EMPTY; + } + private AccessConditionOption getOption(AccessConditionConfiguration configuration, + AccessConditionDTO accessConditionDTO) { + for (AccessConditionOption option :configuration.getOptions()) { + if (option.getName().equals(accessConditionDTO.getName())) { + return option; + } + } + return null; + } + + private boolean checkDubblication(Context context, AccessConditionConfiguration configuration, + List policies, AccessConditionDTO accessConditionDTO, int toReplace, Item item) + throws SQLException, AuthorizeException, ParseException { + ResourcePolicy rp = policies.get(toReplace); + if (rp.getRpName().equals(accessConditionDTO.getName())) { + boolean canApplay = AccessConditionResourcePolicyUtils.canApplyResourcePolicy(context, + configuration.getOptions(), accessConditionDTO.getName(), + accessConditionDTO.getStartDate(), accessConditionDTO.getEndDate()); + if (canApplay) { + item.getResourcePolicies().remove(rp); + return true; + } + } + for (ResourcePolicy resourcePolicy : policies) { + if (resourcePolicy.getRpName().equals(accessConditionDTO.getName())) { + return false; + } + } + item.getResourcePolicies().remove(rp); + return true; + } + + @Override + protected Class getArrayClassForEvaluation() { + return AccessConditionDTO[].class; + } + + @Override + protected Class getClassForEvaluation() { + return AccessConditionDTO.class; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java new file mode 100644 index 0000000000..b9c5215063 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.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.app.rest.submit.factory.impl; +import java.sql.SQLException; +import java.text.ParseException; +import java.util.Date; +import java.util.List; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.AccessConditionDTO; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.submit.model.AccessConditionOption; + +/** + * Utility class to reuse methods related to resource-policies + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +public class AccessConditionResourcePolicyUtils { + + private AccessConditionResourcePolicyUtils() {} + + public static void findApplyResourcePolicy(Context context, List accessConditionOptions, + DSpaceObject obj, List newAccessConditions) + throws SQLException, AuthorizeException, ParseException { + for (AccessConditionDTO newAccessCondition : newAccessConditions) { + String name = newAccessCondition.getName(); + String description = newAccessCondition.getDescription(); + + Date startDate = newAccessCondition.getStartDate(); + Date endDate = newAccessCondition.getEndDate(); + + findApplyResourcePolicy(context, accessConditionOptions, obj, name, description, startDate, endDate); + } + } + + + public static void findApplyResourcePolicy(Context context, + List accessConditionOptions, DSpaceObject obj, String name, + String description, Date startDate, Date endDate) throws SQLException, AuthorizeException, ParseException { + boolean found = false; + for (AccessConditionOption accessConditionOption : accessConditionOptions) { + if (!found && accessConditionOption.getName().equalsIgnoreCase(name)) { + accessConditionOption.createResourcePolicy(context, obj, name, description, startDate, endDate); + found = true; + } + } + if (!found) { + throw new UnprocessableEntityException("The provided policy: " + name + " is not supported!"); + } + } + + public static boolean canApplyResourcePolicy(Context context, + List accessConditionOptions, String name, Date startDate, Date endDate) + throws SQLException, AuthorizeException, ParseException { + for (AccessConditionOption accessConditionOption : accessConditionOptions) { + if (accessConditionOption.getName().equalsIgnoreCase(name)) { + return accessConditionOption.canCreateResourcePolicy(context, name, startDate, endDate); + } + } + return false; + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index 8de0c3b8dc..bb56393d0b 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -86,7 +86,7 @@ - + From f8626ca541f40901e3202aba1420d37d7d9c3fe8 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Dec 2021 13:42:51 +0100 Subject: [PATCH 0550/1254] added tests --- .../rest/WorkspaceItemRestRepositoryIT.java | 688 +++++++++++++++++- 1 file changed, 676 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index b3db2a74e2..74875c9f76 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 @@ -10,6 +10,7 @@ package org.dspace.app.rest; 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.authorize.ResourcePolicy.TYPE_CUSTOM; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; @@ -26,8 +27,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -38,7 +41,6 @@ import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.time.DateUtils; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.ItemMatcher; @@ -51,8 +53,6 @@ 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.authorize.ResourcePolicy; -import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -62,6 +62,7 @@ import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; @@ -102,8 +103,6 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration private GroupService groupService; - private ResourcePolicyService resourcePolicyService; - private Group embargoedGroups; private Group embargoedGroup1; private Group embargoedGroup2; @@ -115,7 +114,6 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration super.setUp(); context.turnOffAuthorisationSystem(); this.groupService = EPersonServiceFactory.getInstance().getGroupService(); - this.resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); embargoedGroups = GroupBuilder.createGroup(context) .withName("Embargoed Groups") @@ -516,7 +514,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration //Add a bitstream to the item String bitstreamContent = "ThisIsSomeDummyText"; Bitstream bitstream = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + try (InputStream is = IOUtils.toInputStream(bitstreamContent, StandardCharsets.UTF_8)) { bitstream = BitstreamBuilder .createBitstream(context, item, is) .withName("Bitstream1") @@ -564,7 +562,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration //Add a bitstream to the item String bitstreamContent = "ThisIsSomeDummyText"; Bitstream bitstream = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + try (InputStream is = IOUtils.toInputStream(bitstreamContent, StandardCharsets.UTF_8)) { bitstream = BitstreamBuilder .createBitstream(context, item, is) .withName("Bitstream1") @@ -609,7 +607,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration //Add a bitstream to the item String bitstreamContent = "ThisIsSomeDummyText"; Bitstream bitstream = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + try (InputStream is = IOUtils.toInputStream(bitstreamContent, StandardCharsets.UTF_8)) { bitstream = BitstreamBuilder .createBitstream(context, item, is) .withName("Bitstream1") @@ -626,7 +624,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration String bitstreamContent2 = "ThisIsSomeDummyText2"; Bitstream bitstream2 = null; - try (InputStream is2 = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { + try (InputStream is2 = IOUtils.toInputStream(bitstreamContent2, StandardCharsets.UTF_8)) { bitstream2 = BitstreamBuilder .createBitstream(context, item2, is2) .withName("Bitstream 2") @@ -4350,7 +4348,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration } @Test - public void patchAcceptLicenseWrontPathTest() throws Exception { + public void patchAcceptLicenseWrongPathTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -5926,4 +5924,670 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isUnauthorized()); } -} + @Test + public void patchAccesConditionDiscoverableTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + witem.getItem().setDiscoverable(false); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(false))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + + List replaceVisibility = new ArrayList(); + replaceVisibility.add(new ReplaceOperation("/sections/defaultAC/discoverable", true)); + + String patchBody = getPatchContent(replaceVisibility); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + } + + @Test + public void patchAccesConditionDiscoverableWrongValueTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + + List replaceVisibility = new ArrayList(); + replaceVisibility.add(new ReplaceOperation("/sections/defaultAC/discoverable", "wrongValue")); + + String patchBody = getPatchContent(replaceVisibility); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void patcDiscoverableWithAccesConditionConfigurationDiscoverableDisabledTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity, + "123456789/accessCondition-not-discoverable") + .withName("Collection 1") + .build(); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.notDiscoverable.discoverable", is(true))) + .andExpect(jsonPath("$.sections.notDiscoverable.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.notDiscoverable.accessConditions[1].name").doesNotExist()); + + List replaceVisibility = new ArrayList(); + replaceVisibility.add(new ReplaceOperation("/sections/notDiscoverable/discoverable", false)); + + String patchBody = getPatchContent(replaceVisibility); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.notDiscoverable.discoverable", is(true))) + .andExpect(jsonPath("$.sections.notDiscoverable.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.notDiscoverable.accessConditions[1].name").doesNotExist()); + } + + @Test + public void patchAddAccesConditionTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + + List addAccessCondition = new ArrayList(); + Map accessCondition = new HashMap(); + accessCondition.put("name", "administrator"); + addAccessCondition.add(new AddOperation("/sections/defaultAC/accessConditions/-", accessCondition)); + + String patchBody = getPatchContent(addAccessCondition); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + } + + @Test + public void patchAddNotSupportedAccesConditionTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + + List addAccessCondition = new ArrayList(); + Map accessCondition = new HashMap(); + accessCondition.put("name", "notSupported"); + addAccessCondition.add(new AddOperation("/sections/defaultAC/accessConditions/-", accessCondition)); + + String patchBody = getPatchContent(addAccessCondition); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + } + + @Test + public void patchAddDuplicateAccesConditionTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + + List addAccessCondition = new ArrayList(); + Map accessCondition = new HashMap(); + accessCondition.put("name", "openaccess"); + addAccessCondition.add(new AddOperation("/sections/defaultAC/accessConditions/-", accessCondition)); + + String patchBody = getPatchContent(addAccessCondition); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + } + + @Test + public void patchRemoveAllAccesConditionsTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + + List removeAccessConditions = new ArrayList(); + removeAccessConditions.add(new RemoveOperation("/sections/defaultAC/accessConditions")); + + String patchBody = getPatchContent(removeAccessConditions); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name").doesNotExist()); + } + + @Test + public void patchRemoveSpecificAccesConditionsTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); + + Calendar calendar = Calendar.getInstance(); + + calendar.set(Calendar.YEAR, 2020); + calendar.set(Calendar.MONTH, 1); + calendar.set(Calendar.DATE, 1); + + Date data = calendar.getTime(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("embargoed") + .withStartDate(data) + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name", + is("embargoed"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[3].name").doesNotExist()); + + List removeAccessConditions = new ArrayList(); + // we want to remove the second resource policy + removeAccessConditions.add(new RemoveOperation("/sections/defaultAC/accessConditions/1")); + + String patchBody = getPatchContent(removeAccessConditions); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("embargoed"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + } + + @Test + public void patchRemoveFirstAccesConditionsTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + + List removeAccessConditions = new ArrayList(); + // we want to remove the second resource policy + removeAccessConditions.add(new RemoveOperation("/sections/defaultAC/accessConditions/0")); + + String patchBody = getPatchContent(removeAccessConditions); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + } + + @Test + public void patchRemoveAccesConditionsIdxNotSupportedTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + + List removeAccessConditions = new ArrayList(); + // we want to remove the second resource policy + removeAccessConditions.add(new RemoveOperation("/sections/defaultAC/accessConditions/2")); + + String patchBody = getPatchContent(removeAccessConditions); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + } + + @Test + public void patchRemoveAccesConditionsUnprocessableEntityTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + + List removeAccessConditions = new ArrayList(); + // we want to remove the second resource policy + removeAccessConditions.add(new RemoveOperation("/sections/defaultAC/accessConditions/-")); + + String patchBody = getPatchContent(removeAccessConditions); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + } + +} \ No newline at end of file From e76e1a23e6eca9ccaf7a13c35e9737cd850b33cf Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Dec 2021 15:19:39 +0100 Subject: [PATCH 0551/1254] fix failed tests --- .../SubmissionDefinitionsControllerIT.java | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 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 166504b3ea..fe80df54c2 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 @@ -206,7 +206,7 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra // 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(6))) + .andExpect(jsonPath("$._embedded.submissionsections", hasSize(7))) .andExpect(jsonPath("$._embedded.submissionsections", Matchers.hasItem( allOf( @@ -258,10 +258,10 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(4))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -269,7 +269,7 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra .param("page", "1")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("languagetestprocess"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("accessConditionNotDiscoverable"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -284,10 +284,10 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(4))) .andExpect(jsonPath("$.page.number", is(1))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -295,24 +295,47 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra .param("page", "2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("languagetestprocess"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(4))) + .andExpect(jsonPath("$.page.number", is(2))); + + getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") + .param("size", "1") + .param("page", "3")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("extractiontestprocess"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=1"), Matchers.containsString("size=1")))) + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.next").doesNotExist()) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) - .andExpect(jsonPath("$.page.number", is(2))); + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(4))) + .andExpect(jsonPath("$.page.number", is(3))); } } From 3ce83c0087f45991df3ab33457a5b184bf68cce3 Mon Sep 17 00:00:00 2001 From: Joost Date: Tue, 14 Dec 2021 16:01:51 +0100 Subject: [PATCH 0552/1254] adapted submissionDefinitionsControllerIT to expect the qualdroptest submission definition --- .../SubmissionDefinitionsControllerIT.java | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 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 166504b3ea..6715c5cfcf 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 @@ -258,10 +258,10 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(4))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -284,10 +284,10 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(4))) .andExpect(jsonPath("$.page.number", is(1))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -295,24 +295,50 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra .param("page", "2")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("extractiontestprocess"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("qualdroptest"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=1"), Matchers.containsString("size=1")))) - .andExpect(jsonPath("$._links.next").doesNotExist()) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.self.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=2"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(4))) .andExpect(jsonPath("$.page.number", is(2))); + + getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") + .param("size", "1") + .param("page", "3")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("extractiontestprocess"))) + .andExpect(jsonPath("$._links.first.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=0"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.prev.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=2"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.next").doesNotExist()) + .andExpect(jsonPath("$._links.self.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$._links.last.href", Matchers.allOf( + Matchers.containsString("/api/config/submissiondefinitions?"), + Matchers.containsString("page=3"), Matchers.containsString("size=1")))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(4))) + .andExpect(jsonPath("$.page.number", is(3))); } } From f13bb90a073945d649ba871d73c62870864af216 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 2 Dec 2021 16:30:04 -0800 Subject: [PATCH 0553/1254] Added collection metadata check for iiif enabled. --- .../org/dspace/app/iiif/service/utils/IIIFUtils.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 11e82a3f2f..79c844948f 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 @@ -93,16 +93,20 @@ public class IIIFUtils { } /** - * This method verify if the IIIF feature is enabled on the item + * This method verify if the IIIF feature is enabled on the item or parent collection. * * @param item the dspace item * @return true if the item supports IIIF */ public boolean isIIIFEnabled(Item item) { - return item.getMetadata().stream() + return item.getOwningCollection().getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")) + || item.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")); + m.getValue().equalsIgnoreCase("yes")); } /** From 0695d4f449b1e5b56dd061a0d63dac5397da147f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 9 Dec 2021 12:52:55 -0800 Subject: [PATCH 0554/1254] Initial work on bitstream IIIF processor. --- .../canvasdimension/CanvasDimensionCLI.java | 130 ++++++++++ .../IIIFCanvasDimensionProcessor.java | 238 ++++++++++++++++++ .../canvasdimension/ImageDimensionReader.java | 30 +++ 3 files changed, 398 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java new file mode 100644 index 0000000000..836f355991 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.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.canvasdimension; + +import java.util.Arrays; + +import org.apache.commons.cli.*; +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.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; + +public class CanvasDimensionCLI { + + private CanvasDimensionCLI() {} + + public static void main(String[] argv) throws Exception { + + boolean force = false; + String identifier = null; + int max2Process = Integer.MAX_VALUE; + + Context context = new Context(); + IIIFCanvasDimensionProcessor canvasProcessor = new IIIFCanvasDimensionProcessor(); + + CommandLineParser parser = new DefaultParser(); + + Options options = new Options(); + options.addOption("i", "identifier", true, + "process IIIF canvas dimensions for images belonging to identifier"); + options.addOption("f", "force", false, + "force update of all IIIF canvas height and width dimensions"); + options.addOption("s", "skipList", false, + "force update of all IIIF canvas height and width dimensions"); + options.addOption("m", "maximum", true, + "process no more than maximum items"); + //create a "skip" option (to specify communities/collections/items to skip) + Option skipOption = Option.builder("s") + .longOpt("skip") + .hasArg() + .hasArgs() + .valueSeparator(',') + .desc( + "SKIP the bitstreams belonging to identifier\n" + + "Separate multiple identifiers with a comma (,)\n" + + "(e.g. -s \n 123456789/34,123456789/323)") + .build(); + options.addOption(skipOption); + + CommandLine line = null; + try { + line = parser.parse(options, argv); + } catch (MissingArgumentException e) { + System.out.println("ERROR: " + e.getMessage()); + HelpFormatter myhelp = new HelpFormatter(); + myhelp.printHelp("CanvasDimension processor\n", options); + System.exit(1); + } + + if (line.hasOption('f')) { + force = true; + } + if (line.hasOption('i')) { + identifier = line.getOptionValue('i'); + } else { + System.out.println("An identifier for a Community, Collection, or Item must be provided."); + } + if (line.hasOption('m')) { + max2Process = Integer.parseInt(line.getOptionValue('m')); + if (max2Process <= 1) { + System.out.println("Invalid maximum value '" + + line.getOptionValue('m') + "' - ignoring"); + max2Process = Integer.MAX_VALUE; + } + } + String skipIds[] = null; + + if (line.hasOption('s')) { + //specified which identifiers to skip when processing + skipIds = line.getOptionValues('s'); + + if (skipIds == null || skipIds.length == 0) { //display error, since no identifiers specified to skip + System.err.println("\nERROR: -s (-skip) option requires at least one identifier to SKIP.\n" + + "Make sure to separate multiple identifiers with a comma!\n" + + "(e.g. -s 123456789/34,123456789/323)\n"); + HelpFormatter myhelp = new HelpFormatter(); + myhelp.printHelp("MediaFilterManager\n", options); + System.exit(0); + } + canvasProcessor.setSkipList(Arrays.asList(skipIds)); + } + + + DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, identifier); + if (dso == null) { + throw new IllegalArgumentException("Cannot resolve " + + identifier + " to a DSpace object"); + } + + + canvasProcessor.setForceProcessing(force); + canvasProcessor.setMax2Process(max2Process); + + switch (dso.getType()) { + case Constants.SITE: + canvasProcessor.processSite(context); + break; + case Constants.COMMUNITY: + canvasProcessor.processCommunity(context, (Community) dso); + break; + case Constants.COLLECTION: + canvasProcessor.processCollection(context, (Collection) dso); + break; + case Constants.ITEM: + canvasProcessor.processItem(context, (Item) dso); + break; + default: + break; + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java new file mode 100644 index 0000000000..8541ab6668 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java @@ -0,0 +1,238 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.canvasdimension; + +import java.io.InputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.CommunityService; +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.license.CreativeCommonsServiceImpl; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +public class IIIFCanvasDimensionProcessor implements InitializingBean { + + @Autowired + ItemService itemService; + @Autowired + CommunityService communityService; + @Autowired + BitstreamService bitstreamService; + @Autowired + DSpaceObjectService dSpaceObjectService; + + // metadata used to enable the iiif features on the item + public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; + private static final String IIIF_WIDTH_METADATA = "iiif.image.width"; + // The DSpace bundle for other content related to item. + protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; + + private boolean forceProcessing = false; + private boolean isQuiet = false; + private List skipList = null; + private int max2Process = Integer.MAX_VALUE; // TODO no option for this yet. + private int processed = 0; + protected Item currentItem = null; // TODO needed? + + @Override + public void afterPropertiesSet() throws Exception { + + } + + public void setForceProcessing(boolean force) { + forceProcessing = force; + } + + public void setMax2Process(int max2Process) { + this.max2Process = max2Process; + } + + public void setSkipList(List skipList) { + this.skipList = skipList; + } + + public void processSite(Context context) throws Exception { + if (skipList != null) { + //if a skip-list exists, we need to filter community-by-community + //so we can respect what is in the skip-list + List topLevelCommunities = communityService.findAllTop(context); + + for (Community topLevelCommunity : topLevelCommunities) { + processCommunity(context, topLevelCommunity); + } + } else { + //otherwise, just find every item and process + Iterator itemIterator = itemService.findAll(context); + while (itemIterator.hasNext() && processed < max2Process) { + processItem(context, itemIterator.next()); + } + } + } + + public void processCommunity(Context context, Community community) throws Exception { + if (!inSkipList(community.getHandle())) { + List subcommunities = community.getSubcommunities(); + for (Community subcommunity : subcommunities) { + processCommunity(context, subcommunity); + } + List collections = community.getCollections(); + for (Collection collection : collections) { + processCollection(context, collection); + } + } + } + + public void processCollection(Context context, Collection collection) throws Exception { + if (!inSkipList(collection.getHandle())) { + Iterator itemIterator = itemService.findAllByCollection(context, collection); + while (itemIterator.hasNext() && processed < max2Process) { + processItem(context, itemIterator.next()); + } + } + } + + public void processItem(Context context, Item item) throws Exception { + if (!inSkipList(item.getHandle())) { + boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') + .contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")); + if (isIIIFItem) { + if (processBundles(context, item)) { + ++processed; + } + } + } + } + + private boolean processBundles(Context context, Item item) throws Exception { + List bundles = getIIIFBundles(item); + boolean done = false; + for (Bundle bundle : bundles) { + List myBitstreams = bundle.getBitstreams(); + for (Bitstream myBitstream : myBitstreams) { + done |= processBitstream(context, item, myBitstream); + } + } + return done; + + } + + private boolean processBitstream(Context context, Item item, Bitstream bitstream) throws Exception { + boolean processed = false; + boolean isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); + if (isImage) { + Optional op = bitstream.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') + .contentEquals(IIIF_WIDTH_METADATA)).findFirst(); + if (op.isEmpty() || forceProcessing) { + InputStream srcStream = bitstreamService.retrieve(context, bitstream); + int[] dims = ImageDimensionReader.getImageDimensions(srcStream); + if (dims != null) { + processed = setBitstreamMetadata(context, bitstream, dims); + } + } + } + return processed; + } + + private boolean setBitstreamMetadata(Context context, Bitstream bitstream, int[] dims) { + try { + dSpaceObjectService.clearMetadata(context, bitstream, "iiif", + "image", "width", Item.ANY); + dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", + "image", "width", null, + String.valueOf(dims[0]), null, -1, -1); + dSpaceObjectService.clearMetadata(context, bitstream, "iiif", + "image", "height", Item.ANY); + dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", + "image", "height", null, + String.valueOf(dims[1]), null, -1, -1); + return true; + } catch (SQLException e) { + System.out.println("Unable to update metadata: " + e.getMessage()); + return false; + } + } + + private boolean inSkipList(String identifier) { + if (skipList != null && skipList.contains(identifier)) { + if (!isQuiet) { + System.out.println("SKIP-LIST: skipped bitstreams within identifier " + identifier); + } + return true; + } else { + return false; + } + } + + /** + * This method returns the bundles holding IIIF resources if any. + * If there is no IIIF content available an empty bundle list is returned. + * @param item the DSpace item + * + * @return list of DSpace bundles with IIIF content + */ + private List getIIIFBundles(Item item) { + boolean iiif = isIIIFEnabled(item); + List bundles = new ArrayList<>(); + if (iiif) { + bundles = item.getBundles().stream().filter(b -> isIIIFBundle(b)).collect(Collectors.toList()); + } + return bundles; + } + + /** + * This method verify if the IIIF feature is enabled on the item or parent collection. + * + * @param item the dspace item + * @return true if the item supports IIIF + */ + private boolean isIIIFEnabled(Item item) { + return item.getOwningCollection().getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")) + || item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")); + } + + /** + * Utility method to check is a bundle can contain bitstreams to use as IIIF + * resources + * + * @param b the DSpace bundle to check + * @return true if the bundle can contain bitstreams to use as IIIF resources + */ + private boolean isIIIFBundle(Bundle b) { + return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, + Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", + "BRANDED_PREVIEW", "TEXT", OTHER_CONTENT_BUNDLE) + && b.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java new file mode 100644 index 0000000000..bb7e2389c2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.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.canvasdimension; + +import java.awt.image.BufferedImage; +import java.io.InputStream; +import javax.imageio.ImageIO; + +public class ImageDimensionReader { + + private ImageDimensionReader() {} + + public static int[] getImageDimensions(InputStream image) throws Exception { + int[] dims = new int[2]; + BufferedImage buf = ImageIO.read(image); + int width = buf.getWidth(); + int height = buf.getHeight(); + if (width > 0 && height > 0) { + dims[0] = width; + dims[1] = height; + return dims; + } + return null; + } +} From a009a244748c9ce7a250b5474d61c08530c6892f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 9 Dec 2021 13:05:17 -0800 Subject: [PATCH 0555/1254] Added more options. --- .../canvasdimension/CanvasDimensionCLI.java | 7 +++ .../IIIFCanvasDimensionProcessor.java | 60 ++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java index 836f355991..54eea253f5 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java @@ -25,6 +25,7 @@ public class CanvasDimensionCLI { public static void main(String[] argv) throws Exception { boolean force = false; + boolean isQuiet = false; String identifier = null; int max2Process = Integer.MAX_VALUE; @@ -38,6 +39,8 @@ public class CanvasDimensionCLI { "process IIIF canvas dimensions for images belonging to identifier"); options.addOption("f", "force", false, "force update of all IIIF canvas height and width dimensions"); + options.addOption("q", "quiet", false, + "do not print anything except in the event of errors."); options.addOption("s", "skipList", false, "force update of all IIIF canvas height and width dimensions"); options.addOption("m", "maximum", true, @@ -68,6 +71,9 @@ public class CanvasDimensionCLI { if (line.hasOption('f')) { force = true; } + if (line.hasOption('q')) { + isQuiet = true; + } if (line.hasOption('i')) { identifier = line.getOptionValue('i'); } else { @@ -108,6 +114,7 @@ public class CanvasDimensionCLI { canvasProcessor.setForceProcessing(force); canvasProcessor.setMax2Process(max2Process); + canvasProcessor.setIsQuiet(isQuiet); switch (dso.getType()) { case Constants.SITE: diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java index 8541ab6668..c647ce8741 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java @@ -60,18 +60,40 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } + /** + * Set the force processing property. If true, existing canvas + * metadata will be replaced. + * @param force + */ public void setForceProcessing(boolean force) { forceProcessing = force; } + public void setIsQuiet(boolean quiet) { + isQuiet = quiet; + } + + /** + * Set the maximum number of items to process. + * @param max2Process + */ public void setMax2Process(int max2Process) { this.max2Process = max2Process; } + /** + * Set dso identifiers to skip. + * @param skipList + */ public void setSkipList(List skipList) { this.skipList = skipList; } + /** + * Set IIIF canvas dimensions on all IIIF items in the site. + * @param context + * @throws Exception + */ public void processSite(Context context) throws Exception { if (skipList != null) { //if a skip-list exists, we need to filter community-by-community @@ -90,6 +112,13 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } } + /** + * Set IIIF canvas dimensions on all IIIF items in a community and its + * sub-communities. + * @param context + * @param community + * @throws Exception + */ public void processCommunity(Context context, Community community) throws Exception { if (!inSkipList(community.getHandle())) { List subcommunities = community.getSubcommunities(); @@ -103,6 +132,12 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } } + /** + * Set IIIF canvas dimensions on all IIIF items in a collection. + * @param context + * @param collection + * @throws Exception + */ public void processCollection(Context context, Collection collection) throws Exception { if (!inSkipList(collection.getHandle())) { Iterator itemIterator = itemService.findAllByCollection(context, collection); @@ -112,6 +147,12 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } } + /** + * Set IIIF canvas dimensions for an item. + * @param context + * @param item + * @throws Exception + */ public void processItem(Context context, Item item) throws Exception { if (!inSkipList(item.getHandle())) { boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') @@ -126,20 +167,35 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { } } + /** + * Process all IIIF bundles for an item. + * @param context + * @param item + * @return + * @throws Exception + */ private boolean processBundles(Context context, Item item) throws Exception { List bundles = getIIIFBundles(item); boolean done = false; for (Bundle bundle : bundles) { List myBitstreams = bundle.getBitstreams(); for (Bitstream myBitstream : myBitstreams) { - done |= processBitstream(context, item, myBitstream); + done |= processBitstream(context, myBitstream); } } return done; } - private boolean processBitstream(Context context, Item item, Bitstream bitstream) throws Exception { + /** + * Sets the IIIF height and width metadata for all images. If width metadata already exists, + * the bitstream is processed only if forceProcessing is true. + * @param context + * @param bitstream + * @return + * @throws Exception + */ + private boolean processBitstream(Context context, Bitstream bitstream) throws Exception { boolean processed = false; boolean isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); if (isImage) { From e07f5624690a8307ac9945fb57b4daa9841f0f9f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Fri, 10 Dec 2021 17:14:17 -0800 Subject: [PATCH 0556/1254] Added added service for image formats not supported by ImageIO (jp2) --- .../canvasdimension/CanvasDimensionCLI.java | 42 +++++++++++- .../IIIFApiQueryServiceImpl.java | 65 ++++++++++++++++++ ...va => IIIFCanvasDimensionServiceImpl.java} | 68 ++++++++++++------- .../canvasdimension/ImageDimensionReader.java | 4 +- .../IIIFCanvasDimensionServiceFactory.java | 15 ++++ ...IIIFCanvasDimensionServiceFactoryImpl.java | 15 ++++ .../service/IIIFApiQueryService.java | 9 +++ .../service/IIIFCanvasDimensionService.java | 30 ++++++++ dspace/config/launcher.xml | 7 ++ .../spring/api/core-factory-services.xml | 1 + dspace/config/spring/api/core-services.xml | 3 + 11 files changed, 230 insertions(+), 29 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java rename dspace-api/src/main/java/org/dspace/app/canvasdimension/{IIIFCanvasDimensionProcessor.java => IIIFCanvasDimensionServiceImpl.java} (82%) create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java index 54eea253f5..4b560390ca 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java @@ -8,18 +8,30 @@ package org.dspace.app.canvasdimension; import java.util.Arrays; +import java.util.UUID; import org.apache.commons.cli.*; +import org.dspace.app.canvasdimension.factory.IIIFCanvasDimensionServiceFactory; +import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; 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.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; public class CanvasDimensionCLI { + private static final EPersonService epersonService = EPersonServiceFactory.getInstance().getEPersonService(); + private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance() + .getConfigurationService(); + private CanvasDimensionCLI() {} public static void main(String[] argv) throws Exception { @@ -27,16 +39,26 @@ public class CanvasDimensionCLI { boolean force = false; boolean isQuiet = false; String identifier = null; + String eperson = null; int max2Process = Integer.MAX_VALUE; + boolean iiifEnabled = configurationService.getBooleanProperty("iiif.enabled"); + + if (!iiifEnabled) { + System.out.println("IIIF is not enabled on this DSpace server."); + return; + } Context context = new Context(); - IIIFCanvasDimensionProcessor canvasProcessor = new IIIFCanvasDimensionProcessor(); + IIIFCanvasDimensionService canvasProcessor = IIIFCanvasDimensionServiceFactory.getInstance() + .getIiifCanvasDimensionService(); CommandLineParser parser = new DefaultParser(); Options options = new Options(); options.addOption("i", "identifier", true, "process IIIF canvas dimensions for images belonging to identifier"); + options.addOption("e", "eperson", true, + "email of eperson setting canvas dimensions"); options.addOption("f", "force", false, "force update of all IIIF canvas height and width dimensions"); options.addOption("q", "quiet", false, @@ -74,6 +96,9 @@ public class CanvasDimensionCLI { if (line.hasOption('q')) { isQuiet = true; } + if (line.hasOption('e')) { + eperson = line.getOptionValue('e'); + } if (line.hasOption('i')) { identifier = line.getOptionValue('i'); } else { @@ -111,6 +136,21 @@ public class CanvasDimensionCLI { + identifier + " to a DSpace object"); } + EPerson user; + + if (eperson.indexOf('@') != -1) { + // @ sign, must be an email + user = epersonService.findByEmail(context, eperson); + } else { + user = epersonService.find(context, UUID.fromString(eperson)); + } + + if (user == null) { + System.out.println("Error, eperson cannot be found: " + eperson); + System.exit(1); + } + + context.setCurrentUser(user); canvasProcessor.setForceProcessing(force); canvasProcessor.setMax2Process(max2Process); diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java new file mode 100644 index 0000000000..b31658d535 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java @@ -0,0 +1,65 @@ +package org.dspace.app.canvasdimension; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.canvasdimension.service.IIIFApiQueryService; +import org.dspace.content.Bitstream; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +public class IIIFApiQueryServiceImpl implements IIIFApiQueryService, InitializingBean { + + @Autowired(required = true) + protected ConfigurationService configurationService; + + String iiifImageServer; + + @Override + public void afterPropertiesSet() throws Exception { + iiifImageServer = configurationService.getProperty("iiif.image.server"); + } + + public int[] getImageDimensions(Bitstream bitstream) { + return getIiifImageDimensions(bitstream); + } + + /** + * Retrieves image dimensions from the image server (IIIF Image API v.2.1.1). + * @param bitstream the bitstream DSO + * @return image dimensions + */ + private int[] getIiifImageDimensions(Bitstream bitstream) { + int[] arr = new int[2]; + String path = iiifImageServer + bitstream.getID() + "/info.json"; + URL url; + try { + url = new URL(path); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuilder response = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + JsonNode parent = new ObjectMapper().readTree(response.toString()); + arr[0] = parent.get("width").asInt(); + arr[1] = parent.get("height").asInt(); + return arr; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java similarity index 82% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java rename to dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java index c647ce8741..29b08ba5d2 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionProcessor.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java @@ -16,11 +16,14 @@ import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.canvasdimension.service.IIIFApiQueryService; +import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.MetadataValue; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectService; @@ -28,19 +31,20 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.license.CreativeCommonsServiceImpl; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; -public class IIIFCanvasDimensionProcessor implements InitializingBean { +public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionService { - @Autowired + @Autowired(required = true) ItemService itemService; - @Autowired + @Autowired(required = true) CommunityService communityService; - @Autowired + @Autowired(required = true) BitstreamService bitstreamService; - @Autowired + @Autowired(required = true) DSpaceObjectService dSpaceObjectService; + @Autowired(required = true) + IIIFApiQueryService iiifApiQuery; // metadata used to enable the iiif features on the item public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; @@ -55,20 +59,17 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { private int processed = 0; protected Item currentItem = null; // TODO needed? - @Override - public void afterPropertiesSet() throws Exception { - - } - /** * Set the force processing property. If true, existing canvas * metadata will be replaced. * @param force */ + @Override public void setForceProcessing(boolean force) { forceProcessing = force; } + @Override public void setIsQuiet(boolean quiet) { isQuiet = quiet; } @@ -77,6 +78,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * Set the maximum number of items to process. * @param max2Process */ + @Override public void setMax2Process(int max2Process) { this.max2Process = max2Process; } @@ -85,6 +87,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * Set dso identifiers to skip. * @param skipList */ + @Override public void setSkipList(List skipList) { this.skipList = skipList; } @@ -94,6 +97,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @param context * @throws Exception */ + @Override public void processSite(Context context) throws Exception { if (skipList != null) { //if a skip-list exists, we need to filter community-by-community @@ -119,6 +123,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @param community * @throws Exception */ + @Override public void processCommunity(Context context, Community community) throws Exception { if (!inSkipList(community.getHandle())) { List subcommunities = community.getSubcommunities(); @@ -138,6 +143,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @param collection * @throws Exception */ + @Override public void processCollection(Context context, Collection collection) throws Exception { if (!inSkipList(collection.getHandle())) { Iterator itemIterator = itemService.findAllByCollection(context, collection); @@ -153,6 +159,7 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @param item * @throws Exception */ + @Override public void processItem(Context context, Item item) throws Exception { if (!inSkipList(item.getHandle())) { boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') @@ -160,8 +167,10 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || m.getValue().equalsIgnoreCase("yes")); if (isIIIFItem) { - if (processBundles(context, item)) { + if (processItemBundles(context, item)) { ++processed; + // commit changes + context.commit(); } } } @@ -174,15 +183,16 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { * @return * @throws Exception */ - private boolean processBundles(Context context, Item item) throws Exception { + private boolean processItemBundles(Context context, Item item) throws Exception { List bundles = getIIIFBundles(item); boolean done = false; for (Bundle bundle : bundles) { - List myBitstreams = bundle.getBitstreams(); - for (Bitstream myBitstream : myBitstreams) { - done |= processBitstream(context, myBitstream); + List bitstreams = bundle.getBitstreams(); + for (Bitstream bit : bitstreams) { + done |= processBitstream(context, bit); } } + itemService.update(context, item); return done; } @@ -197,15 +207,23 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { */ private boolean processBitstream(Context context, Bitstream bitstream) throws Exception { boolean processed = false; + boolean isUnsupported = bitstream.getFormat(context).getMIMEType().contains("image/jp2"); boolean isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); if (isImage) { - Optional op = bitstream.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') - .contentEquals(IIIF_WIDTH_METADATA)).findFirst(); + Optional op = bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.') + .contentEquals(IIIF_WIDTH_METADATA)).findFirst(); if (op.isEmpty() || forceProcessing) { - InputStream srcStream = bitstreamService.retrieve(context, bitstream); - int[] dims = ImageDimensionReader.getImageDimensions(srcStream); + int[] dims; + if (isUnsupported) { + dims = iiifApiQuery.getImageDimensions(bitstream); + } else { + InputStream stream = bitstreamService.retrieve(context, bitstream); + dims = ImageDimensionReader.getImageDimensions(stream); + } if (dims != null) { processed = setBitstreamMetadata(context, bitstream, dims); + bitstreamService.update(context, bitstream); } } } @@ -216,14 +234,12 @@ public class IIIFCanvasDimensionProcessor implements InitializingBean { try { dSpaceObjectService.clearMetadata(context, bitstream, "iiif", "image", "width", Item.ANY); - dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", - "image", "width", null, - String.valueOf(dims[0]), null, -1, -1); + dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", + "image", "width", Item.ANY, String.valueOf(dims[0])); dSpaceObjectService.clearMetadata(context, bitstream, "iiif", "image", "height", Item.ANY); - dSpaceObjectService.addAndShiftRightMetadata(context, bitstream, "iiif", - "image", "height", null, - String.valueOf(dims[1]), null, -1, -1); + dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", + "image", "height", Item.ANY, String.valueOf(dims[1])); return true; } catch (SQLException e) { System.out.println("Unable to update metadata: " + e.getMessage()); diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java index bb7e2389c2..f8651d8116 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java @@ -18,8 +18,8 @@ public class ImageDimensionReader { public static int[] getImageDimensions(InputStream image) throws Exception { int[] dims = new int[2]; BufferedImage buf = ImageIO.read(image); - int width = buf.getWidth(); - int height = buf.getHeight(); + int width = buf.getWidth(null); + int height = buf.getHeight(null); if (width > 0 && height > 0) { dims[0] = width; dims[1] = height; diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java new file mode 100644 index 0000000000..6cdac16cf0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java @@ -0,0 +1,15 @@ +package org.dspace.app.canvasdimension.factory; + +import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; +import org.dspace.services.factory.DSpaceServicesFactory; + +public abstract class IIIFCanvasDimensionServiceFactory { + + public static IIIFCanvasDimensionServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("iiifCanvasDimensionServiceFactory", + IIIFCanvasDimensionServiceFactory.class); + } + + public abstract IIIFCanvasDimensionService getIiifCanvasDimensionService(); +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java new file mode 100644 index 0000000000..19f31f145b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java @@ -0,0 +1,15 @@ +package org.dspace.app.canvasdimension.factory; + +import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; +import org.springframework.beans.factory.annotation.Autowired; + +public class IIIFCanvasDimensionServiceFactoryImpl extends IIIFCanvasDimensionServiceFactory { + + @Autowired(required = true) + private IIIFCanvasDimensionService iiifCanvasDimensionService; + + @Override + public IIIFCanvasDimensionService getIiifCanvasDimensionService() { + return iiifCanvasDimensionService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java new file mode 100644 index 0000000000..87277905c4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java @@ -0,0 +1,9 @@ +package org.dspace.app.canvasdimension.service; + +import org.dspace.content.Bitstream; + +public interface IIIFApiQueryService { + + public int[] getImageDimensions(Bitstream bitstream); + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java new file mode 100644 index 0000000000..518634edd6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java @@ -0,0 +1,30 @@ +package org.dspace.app.canvasdimension.service; + +import java.util.List; + +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; + +public interface IIIFCanvasDimensionService { + + public void processSite(Context context) throws Exception; + + public void processCommunity(Context context, Community community) throws Exception; + + public void processCollection(Context context, Collection collection) throws Exception; + + public void processItem(Context context, Item item) throws Exception; + + public void setForceProcessing(boolean force); + + public void setIsQuiet(boolean quiet); + + public void setMax2Process(int max2Process); + + public void setSkipList(List skipList); + + + +} diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index d92e0e5880..7e26e6a3af 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -366,4 +366,11 @@ org.dspace.statistics.AnonymizeStatistics + + canvas-dimensions + Add canvas width and height metadata for IIIF enabled items. + + org.dspace.app.canvasdimension.CanvasDimensionCLI + + diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index 2712ad21d0..e8f5a9b164 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -10,6 +10,7 @@ + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 413d0b814a..886929f026 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -17,6 +17,9 @@ + + + From 24a2f8c2e4afb3973be551a7ff839cf191a54f3d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Dec 2021 18:14:28 +0100 Subject: [PATCH 0557/1254] implemented replace operation for access condition in submission --- .../AccessConditionReplacePatchOperation.java | 160 ++++++++++-------- .../AccessConditionResourcePolicyUtils.java | 2 +- 2 files changed, 92 insertions(+), 70 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java index 95c33c5770..3410e318e8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java @@ -8,6 +8,8 @@ package org.dspace.app.rest.submit.factory.impl; import java.sql.SQLException; import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; @@ -56,83 +58,41 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< AccessConditionDTO accessConditionDTO = evaluateSingleObject((LateObjectEvaluator) value); if (Objects.nonNull(accessConditionDTO) && Objects.nonNull(getOption(configuration, accessConditionDTO))) { List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); - if ((toReplace < 0 || toReplace >= policies.size()) && policies.isEmpty()) { + if (toReplace < 0 || toReplace >= policies.size()) { throw new UnprocessableEntityException("The provided index:" + toReplace + " is not supported," + " currently the are " + policies.size() + " access conditions"); } - if (checkDubblication(context, configuration, policies, accessConditionDTO, toReplace, item)) { + verifyAccessCondition(context, configuration, accessConditionDTO); + if (checkDuplication(context, policies, accessConditionDTO, toReplace, item)) { context.commit(); resourcePolicyService.delete(context, policies.get(toReplace)); - getOption(configuration, accessConditionDTO).createPolicy(context, item, - accessConditionDTO.getName(), null, accessConditionDTO.getStartDate(), - accessConditionDTO.getEndDate()); + AccessConditionOption option = getOption(configuration, accessConditionDTO); + option.createPolicy(context, item, accessConditionDTO.getName(), null, + accessConditionDTO.getStartDate(),accessConditionDTO.getEndDate()); } } } else if (split.length == 3) { - String valueToReplare = getValue(value); + String valueToReplace = getValue(value); int toReplace = Integer.parseInt(split[1]); String attributeReplace = split[2]; List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); - if ((toReplace < 0 || toReplace >= policies.size()) && policies.isEmpty()) { + if (toReplace < 0 || toReplace >= policies.size()) { throw new UnprocessableEntityException("The provided index:" + toReplace + " is not supported," + " currently the are " + policies.size() + " access conditions"); } ResourcePolicy rpToReplace = policies.get(toReplace); - AccessConditionDTO accessConditionDTO = createDTO(rpToReplace, attributeReplace, valueToReplare); - boolean canApplay = AccessConditionResourcePolicyUtils.canApplyResourcePolicy(context, - configuration.getOptions(), accessConditionDTO.getName(), accessConditionDTO.getStartDate(), - accessConditionDTO.getEndDate()); - if (canApplay) { - switch (attributeReplace) { - case "name": - rpToReplace.setRpName(valueToReplare); - break; - case "startDate": - rpToReplace.setStartDate(new Date(valueToReplare)); - break; - case "endDate": - rpToReplace.setEndDate(new Date(valueToReplare)); - break; - default: - } - } - resourcePolicyService.update(context, rpToReplace); + AccessConditionDTO accessConditionDTO = createDTO(rpToReplace, attributeReplace, valueToReplace); + verifyAccessCondition(context, configuration, accessConditionDTO); + updatePolicy(context, valueToReplace, attributeReplace, rpToReplace); } } - private AccessConditionDTO createDTO(ResourcePolicy rpToReplace, String attributeReplace, String valueToReplare) { - AccessConditionDTO accessCondition = new AccessConditionDTO(); - switch (attributeReplace) { - case "name": - accessCondition.setName(valueToReplare); - accessCondition.setStartDate(rpToReplace.getStartDate()); - accessCondition.setEndDate(rpToReplace.getEndDate()); - return accessCondition; - case "startDate": - accessCondition.setName(rpToReplace.getRpName()); - accessCondition.setStartDate(new Date(valueToReplare)); - accessCondition.setEndDate(rpToReplace.getEndDate()); - return accessCondition; - case "endDate": - accessCondition.setName(rpToReplace.getRpName()); - accessCondition.setStartDate(rpToReplace.getStartDate()); - accessCondition.setEndDate(new Date(valueToReplare)); - return accessCondition; - default: - throw new UnprocessableEntityException("The provided attribute: " - + attributeReplace + " is not supported"); - } + private void verifyAccessCondition(Context context, AccessConditionConfiguration configuration, + AccessConditionDTO dto) throws SQLException, AuthorizeException, ParseException { + AccessConditionResourcePolicyUtils.canApplyResourcePolicy(context, configuration.getOptions(), + dto.getName(), dto.getStartDate(), dto.getEndDate()); } - private String getValue(Object value) { - if (value instanceof JsonValueEvaluator) { - JsonValueEvaluator jsonValue = (JsonValueEvaluator) value; - if (jsonValue.getValueNode().fields().hasNext()) { - return jsonValue.getValueNode().fields().next().getValue().asText(); - } - } - return StringUtils.EMPTY; - } private AccessConditionOption getOption(AccessConditionConfiguration configuration, AccessConditionDTO accessConditionDTO) { for (AccessConditionOption option :configuration.getOptions()) { @@ -143,28 +103,90 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< return null; } - private boolean checkDubblication(Context context, AccessConditionConfiguration configuration, - List policies, AccessConditionDTO accessConditionDTO, int toReplace, Item item) + private boolean checkDuplication(Context context, List policies, + AccessConditionDTO accessConditionDTO, int toReplace, Item item) throws SQLException, AuthorizeException, ParseException { - ResourcePolicy rp = policies.get(toReplace); - if (rp.getRpName().equals(accessConditionDTO.getName())) { - boolean canApplay = AccessConditionResourcePolicyUtils.canApplyResourcePolicy(context, - configuration.getOptions(), accessConditionDTO.getName(), - accessConditionDTO.getStartDate(), accessConditionDTO.getEndDate()); - if (canApplay) { - item.getResourcePolicies().remove(rp); - return true; - } + ResourcePolicy policyToReplace = policies.get(toReplace); + // check if the resource policy is of the same type + if (policyToReplace.getRpName().equals(accessConditionDTO.getName())) { + item.getResourcePolicies().remove(policyToReplace); + return true; } + // check if there is not already a policy of the same type for (ResourcePolicy resourcePolicy : policies) { if (resourcePolicy.getRpName().equals(accessConditionDTO.getName())) { return false; } } - item.getResourcePolicies().remove(rp); + item.getResourcePolicies().remove(policyToReplace); return true; } + private AccessConditionDTO createDTO(ResourcePolicy rpToReplace, String attributeReplace, String valueToReplare) + throws ParseException { + AccessConditionDTO accessCondition = new AccessConditionDTO(); + accessCondition.setName(rpToReplace.getRpName()); + accessCondition.setStartDate(rpToReplace.getStartDate()); + accessCondition.setEndDate(rpToReplace.getEndDate()); + switch (attributeReplace) { + case "name": + accessCondition.setName(valueToReplare); + return accessCondition; + case "startDate": + accessCondition.setStartDate(parsDate(valueToReplare)); + return accessCondition; + case "endDate": + accessCondition.setEndDate(parsDate(valueToReplare)); + return accessCondition; + default: + throw new UnprocessableEntityException("The provided attribute: " + + attributeReplace + " is not supported"); + } + } + + private void updatePolicy(Context context, String valueToReplare, String attributeReplace, + ResourcePolicy rpToReplace) throws SQLException, AuthorizeException { + switch (attributeReplace) { + case "name": + rpToReplace.setRpName(valueToReplare); + break; + case "startDate": + rpToReplace.setStartDate(parsDate(valueToReplare)); + break; + case "endDate": + rpToReplace.setEndDate(parsDate(valueToReplare)); + break; + default: + } + resourcePolicyService.update(context, rpToReplace); + } + + private Date parsDate(String date) { + List knownPatterns = new ArrayList(); + knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd")); + knownPatterns.add(new SimpleDateFormat("dd-MM-yyyy")); + knownPatterns.add(new SimpleDateFormat("yyyy/MM/dd")); + knownPatterns.add(new SimpleDateFormat("dd/MM/yyyy")); + + for (SimpleDateFormat pattern : knownPatterns) { + try { + return pattern.parse(date); + } catch (ParseException pe) { + throw new UnprocessableEntityException(""); + } + } + return null; + } + private String getValue(Object value) { + if (value instanceof JsonValueEvaluator) { + JsonValueEvaluator jsonValue = (JsonValueEvaluator) value; + if (jsonValue.getValueNode().fields().hasNext()) { + return jsonValue.getValueNode().fields().next().getValue().asText(); + } + } + return StringUtils.EMPTY; + } + @Override protected Class getArrayClassForEvaluation() { return AccessConditionDTO[].class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java index b9c5215063..efc2ecf05f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java @@ -65,6 +65,6 @@ public class AccessConditionResourcePolicyUtils { return accessConditionOption.canCreateResourcePolicy(context, name, startDate, endDate); } } - return false; + throw new UnprocessableEntityException("The provided policy: " + name + " is not supported!"); } } \ No newline at end of file From 293a9a0083b001998079e7fcf2fb9098fabe9d7b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Dec 2021 18:14:58 +0100 Subject: [PATCH 0558/1254] added tests for replace operations --- .../rest/WorkspaceItemRestRepositoryIT.java | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) 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 74875c9f76..7945173951 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 @@ -6590,4 +6590,211 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); } + @Test + public void patchReplaceAccesConditionTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("administrator") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + + List replaceAccessCondition = new ArrayList(); + Map accessCondition = new HashMap(); + accessCondition.put("name", "embargo"); + accessCondition.put("startDate", "2021-10-23"); + replaceAccessCondition.add(new ReplaceOperation("/sections/defaultAC/accessConditions/1", + accessCondition)); + + String patchBody = getPatchContent(replaceAccessCondition); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("embargo"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].startDate", + is("2021-10-23"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + } + + @Test + public void patchReplaceAccesConditionsUpdateEmbargoStartDateTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + Calendar calendar = Calendar.getInstance(); + + calendar.set(Calendar.YEAR, 2020); + calendar.set(Calendar.MONTH, 1); + calendar.set(Calendar.DATE, 1); + + Date data = calendar.getTime(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("embargo") + .withStartDate(data) + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("embargo"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].startDate", + is("2020-02-01"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + + List replaceAccessCondition = new ArrayList(); + Map accessCondition = new HashMap(); + accessCondition.put("value", "2021-12-31"); + replaceAccessCondition.add(new ReplaceOperation("/sections/defaultAC/accessConditions/1/startDate", + accessCondition)); + + String patchBody = getPatchContent(replaceAccessCondition); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("embargo"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].startDate", + is("2021-12-31"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); + } + + @Test + public void patchReplaceAccesConditionsFromOpenaccessToAdministratorTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + + List replaceAccessCondition = new ArrayList(); + Map accessCondition = new HashMap(); + accessCondition.put("value", "administrator"); + replaceAccessCondition.add(new ReplaceOperation("/sections/defaultAC/accessConditions/0/name", + accessCondition)); + + String patchBody = getPatchContent(replaceAccessCondition); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + } + } \ No newline at end of file From 003aea525246d9e8fa93c0e58b10e3c311158f15 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Dec 2021 18:45:37 +0100 Subject: [PATCH 0559/1254] add default configuration for access condition section --- .../config/spring/api/access-conditions.xml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/dspace/config/spring/api/access-conditions.xml b/dspace/config/spring/api/access-conditions.xml index 54e58f2e65..9049ea35a9 100644 --- a/dspace/config/spring/api/access-conditions.xml +++ b/dspace/config/spring/api/access-conditions.xml @@ -61,4 +61,25 @@ + + + + + + + + + + + + + + + + + + + + + From 2a2b24387a4b0df06864c7b82e0b0fb060faea5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Dec 2021 20:36:04 +0000 Subject: [PATCH 0560/1254] Bump log4j-api from 2.15.0 to 2.16.0 Bumps log4j-api from 2.15.0 to 2.16.0. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api 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 4b4410f2b4..2980b9436c 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ 2.3.1 9.4.41.v20210516 - 2.15.0 + 2.16.0 2.0.24 3.17 1.7.25 From 8fdcb294fa6f5054e130ecc167f2f05d4458cbb5 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 14 Dec 2021 23:20:53 +0100 Subject: [PATCH 0561/1254] refactoring --- .../AccessConditionAddPatchOperation.java | 20 +++---- .../AccessConditionReplacePatchOperation.java | 43 +++++++------- .../rest/WorkspaceItemRestRepositoryIT.java | 57 +++++++++++++++++++ 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java index 364098bb24..1841a3c87a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.submit.factory.impl; import java.sql.SQLException; import java.text.ParseException; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -52,21 +52,17 @@ public class AccessConditionAddPatchOperation extends AddPatchOperation accessConditions = new ArrayList(); - - if (split.length == 1) { - // to replace completely the access conditions - authorizeService.removePoliciesActionFilter(context, item, Constants.READ); - accessConditions = evaluateArrayObject((LateObjectEvaluator) value); - } else if (split.length == 2) { - // to add an access condition - // contains "-", call index-based accessConditions it make not sense - accessConditions.add(evaluateSingleObject((LateObjectEvaluator) value)); - } + List accessConditions = Arrays.asList(evaluateSingleObject((LateObjectEvaluator) value)); verifyAccessConditions(context, configuration, accessConditions); // check duplicate policy checkDuplication(context, item, accessConditions); + + if (split.length == 1) { + // to replace completely the access conditions + authorizeService.removePoliciesActionFilter(context, item, Constants.READ); + } + // apply policies AccessConditionResourcePolicyUtils.findApplyResourcePolicy(context, configuration.getOptions(), item, accessConditions); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java index 3410e318e8..214ca6fe71 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.submit.factory.impl; import java.sql.SQLException; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; @@ -29,6 +29,8 @@ import org.dspace.core.Context; import org.dspace.submit.model.AccessConditionConfiguration; import org.dspace.submit.model.AccessConditionConfigurationService; import org.dspace.submit.model.AccessConditionOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -38,6 +40,8 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation { + private static final Logger log = LoggerFactory.getLogger(AccessConditionReplacePatchOperation.class); + @Autowired private ResourcePolicyService resourcePolicyService; @Autowired @@ -53,15 +57,16 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< // "path" : "/sections/<:name-of-the-form>/accessConditions/0" String[] split = getAbsolutePath(path).split("/"); Item item = source.getItem(); + int toReplace = Integer.parseInt(split[1]); + List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); + if (toReplace < 0 || toReplace >= policies.size()) { + throw new UnprocessableEntityException("The provided index:" + toReplace + " is not supported," + + " currently the are " + policies.size() + " access conditions"); + } + if (split.length == 2) { - int toReplace = Integer.parseInt(split[1]); AccessConditionDTO accessConditionDTO = evaluateSingleObject((LateObjectEvaluator) value); if (Objects.nonNull(accessConditionDTO) && Objects.nonNull(getOption(configuration, accessConditionDTO))) { - List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); - if (toReplace < 0 || toReplace >= policies.size()) { - throw new UnprocessableEntityException("The provided index:" + toReplace + " is not supported," - + " currently the are " + policies.size() + " access conditions"); - } verifyAccessCondition(context, configuration, accessConditionDTO); if (checkDuplication(context, policies, accessConditionDTO, toReplace, item)) { context.commit(); @@ -73,13 +78,7 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< } } else if (split.length == 3) { String valueToReplace = getValue(value); - int toReplace = Integer.parseInt(split[1]); String attributeReplace = split[2]; - List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); - if (toReplace < 0 || toReplace >= policies.size()) { - throw new UnprocessableEntityException("The provided index:" + toReplace + " is not supported," - + " currently the are " + policies.size() + " access conditions"); - } ResourcePolicy rpToReplace = policies.get(toReplace); AccessConditionDTO accessConditionDTO = createDTO(rpToReplace, attributeReplace, valueToReplace); verifyAccessCondition(context, configuration, accessConditionDTO); @@ -162,21 +161,21 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< } private Date parsDate(String date) { - List knownPatterns = new ArrayList(); - knownPatterns.add(new SimpleDateFormat("yyyy-MM-dd")); - knownPatterns.add(new SimpleDateFormat("dd-MM-yyyy")); - knownPatterns.add(new SimpleDateFormat("yyyy/MM/dd")); - knownPatterns.add(new SimpleDateFormat("dd/MM/yyyy")); - + List knownPatterns = Arrays.asList( + new SimpleDateFormat("yyyy-MM-dd"), + new SimpleDateFormat("dd-MM-yyyy"), + new SimpleDateFormat("yyyy/MM/dd"), + new SimpleDateFormat("dd/MM/yyyy")); for (SimpleDateFormat pattern : knownPatterns) { try { return pattern.parse(date); - } catch (ParseException pe) { - throw new UnprocessableEntityException(""); + } catch (ParseException e) { + log.error(e.getMessage(), e); } } - return null; + throw new UnprocessableEntityException("Provided format of date:" + date + " is not supported!"); } + private String getValue(Object value) { if (value instanceof JsonValueEvaluator) { JsonValueEvaluator jsonValue = (JsonValueEvaluator) value; 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 7945173951..e4c3c1f3d1 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 @@ -6255,6 +6255,63 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); } + @Test + public void patchAddAccesConditionReplaceCompletelyTest() throws Exception { + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + 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("Example Title") + .withIssueDate("2019-11-21") + .withSubject("ExtraEntry") + .build(); + + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(witem.getItem()) + .withPolicyType(TYPE_CUSTOM) + .withName("openaccess") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + + List addAccessCondition = new ArrayList(); + Map accessCondition = new HashMap(); + accessCondition.put("name", "administrator"); + addAccessCondition.add(new AddOperation("/sections/defaultAC/accessConditions", + accessCondition)); + + String patchBody = getPatchContent(addAccessCondition); + getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + } + @Test public void patchRemoveAllAccesConditionsTest() throws Exception { //disable file upload mandatory From 3d9a9a19d61cebc813d2b5d039e4cccecba7b237 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Tue, 14 Dec 2021 16:03:17 -0800 Subject: [PATCH 0562/1254] Various updates plus IT. --- .../canvasdimension/CanvasDimensionCLI.java | 40 +- .../IIIFApiQueryServiceImpl.java | 18 +- .../IIIFCanvasDimensionServiceImpl.java | 130 ++--- .../canvasdimension/ImageDimensionReader.java | 11 +- .../org/dspace/app/canvasdimension/Util.java | 29 + .../IIIFCanvasDimensionServiceFactory.java | 7 + ...IIIFCanvasDimensionServiceFactoryImpl.java | 7 + .../service/IIIFApiQueryService.java | 9 +- .../service/IIIFCanvasDimensionService.java | 25 +- .../src/main/java/org/dspace/iiif/Utils.java | 85 +++ .../canvasdimensions/CanvasDimensionsIT.java | 511 ++++++++++++++++++ .../org/dspace/app/canvasdimensions/cat.jpg | Bin 0 -> 36814 bytes 12 files changed, 750 insertions(+), 122 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java create mode 100644 dspace-api/src/main/java/org/dspace/iiif/Utils.java create mode 100644 dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java create mode 100644 dspace-api/src/test/resources/org/dspace/app/canvasdimensions/cat.jpg diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java index 4b560390ca..f3cebac910 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java @@ -10,7 +10,13 @@ package org.dspace.app.canvasdimension; import java.util.Arrays; import java.util.UUID; -import org.apache.commons.cli.*; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.MissingArgumentException; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; import org.dspace.app.canvasdimension.factory.IIIFCanvasDimensionServiceFactory; import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.content.Collection; @@ -41,11 +47,11 @@ public class CanvasDimensionCLI { String identifier = null; String eperson = null; int max2Process = Integer.MAX_VALUE; - boolean iiifEnabled = configurationService.getBooleanProperty("iiif.enabled"); + boolean iiifEnabled = configurationService.getBooleanProperty("iiif.enabled"); if (!iiifEnabled) { System.out.println("IIIF is not enabled on this DSpace server."); - return; + System.exit(0); } Context context = new Context(); @@ -56,18 +62,16 @@ public class CanvasDimensionCLI { Options options = new Options(); options.addOption("i", "identifier", true, - "process IIIF canvas dimensions for images belonging to identifier"); + "process IIIF canvas dimensions for images belonging to this identifier"); options.addOption("e", "eperson", true, - "email of eperson setting canvas dimensions"); + "email of eperson setting the canvas dimensions"); options.addOption("f", "force", false, "force update of all IIIF canvas height and width dimensions"); options.addOption("q", "quiet", false, "do not print anything except in the event of errors."); - options.addOption("s", "skipList", false, - "force update of all IIIF canvas height and width dimensions"); options.addOption("m", "maximum", true, "process no more than maximum items"); - //create a "skip" option (to specify communities/collections/items to skip) + Option skipOption = Option.builder("s") .longOpt("skip") .hasArg() @@ -81,12 +85,13 @@ public class CanvasDimensionCLI { options.addOption(skipOption); CommandLine line = null; + try { line = parser.parse(options, argv); } catch (MissingArgumentException e) { System.out.println("ERROR: " + e.getMessage()); - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("CanvasDimension processor\n", options); + HelpFormatter help = new HelpFormatter(); + help.printHelp("CanvasDimension processor\n", options); System.exit(1); } @@ -103,6 +108,7 @@ public class CanvasDimensionCLI { identifier = line.getOptionValue('i'); } else { System.out.println("An identifier for a Community, Collection, or Item must be provided."); + System.exit(1); } if (line.hasOption('m')) { max2Process = Integer.parseInt(line.getOptionValue('m')); @@ -112,7 +118,7 @@ public class CanvasDimensionCLI { max2Process = Integer.MAX_VALUE; } } - String skipIds[] = null; + String[] skipIds; if (line.hasOption('s')) { //specified which identifiers to skip when processing @@ -123,8 +129,8 @@ public class CanvasDimensionCLI { "Make sure to separate multiple identifiers with a comma!\n" + "(e.g. -s 123456789/34,123456789/323)\n"); HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MediaFilterManager\n", options); - System.exit(0); + myhelp.printHelp("Canvas Dimensions\n", options); + System.exit(1); } canvasProcessor.setSkipList(Arrays.asList(skipIds)); } @@ -138,6 +144,11 @@ public class CanvasDimensionCLI { EPerson user; + if (eperson == null) { + System.out.println("You must provide an eperson."); + System.exit(1); + } + if (eperson.indexOf('@') != -1) { // @ sign, must be an email user = epersonService.findByEmail(context, eperson); @@ -157,9 +168,6 @@ public class CanvasDimensionCLI { canvasProcessor.setIsQuiet(isQuiet); switch (dso.getType()) { - case Constants.SITE: - canvasProcessor.processSite(context); - break; case Constants.COMMUNITY: canvasProcessor.processCommunity(context, (Community) dso); break; diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java index b31658d535..827d67b4e9 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java @@ -1,5 +1,14 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ package org.dspace.app.canvasdimension; +import static org.dspace.app.canvasdimension.Util.checkDimensions; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -23,9 +32,14 @@ public class IIIFApiQueryServiceImpl implements IIIFApiQueryService, Initializin @Override public void afterPropertiesSet() throws Exception { - iiifImageServer = configurationService.getProperty("iiif.image.server"); + iiifImageServer = configurationService.getProperty("iiif.image.server"); } + /** + * Returns array with canvas height and width + * @param bitstream + * @return + */ public int[] getImageDimensions(Bitstream bitstream) { return getIiifImageDimensions(bitstream); } @@ -54,7 +68,7 @@ public class IIIFApiQueryServiceImpl implements IIIFApiQueryService, Initializin JsonNode parent = new ObjectMapper().readTree(response.toString()); arr[0] = parent.get("width").asInt(); arr[1] = parent.get("height").asInt(); - return arr; + return checkDimensions(arr); } catch (IOException e) { e.printStackTrace(); } diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java index 29b08ba5d2..fee8345db6 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java @@ -9,13 +9,10 @@ package org.dspace.app.canvasdimension; import java.io.InputStream; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.dspace.app.canvasdimension.service.IIIFApiQueryService; import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.content.Bitstream; @@ -28,9 +25,8 @@ import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CommunityService; 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.license.CreativeCommonsServiceImpl; +import org.dspace.iiif.Utils; import org.springframework.beans.factory.annotation.Autowired; public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionService { @@ -46,18 +42,14 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic @Autowired(required = true) IIIFApiQueryService iiifApiQuery; - // metadata used to enable the iiif features on the item - public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; + // field used to check for existing bitstream metadata private static final String IIIF_WIDTH_METADATA = "iiif.image.width"; - // The DSpace bundle for other content related to item. - protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; private boolean forceProcessing = false; private boolean isQuiet = false; private List skipList = null; - private int max2Process = Integer.MAX_VALUE; // TODO no option for this yet. + private int max2Process = Integer.MAX_VALUE; private int processed = 0; - protected Item currentItem = null; // TODO needed? /** * Set the force processing property. If true, existing canvas @@ -69,6 +61,10 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic forceProcessing = force; } + /** + * Set whether to output messages during processing. + * @param quiet + */ @Override public void setIsQuiet(boolean quiet) { isQuiet = quiet; @@ -92,29 +88,6 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic this.skipList = skipList; } - /** - * Set IIIF canvas dimensions on all IIIF items in the site. - * @param context - * @throws Exception - */ - @Override - public void processSite(Context context) throws Exception { - if (skipList != null) { - //if a skip-list exists, we need to filter community-by-community - //so we can respect what is in the skip-list - List topLevelCommunities = communityService.findAllTop(context); - - for (Community topLevelCommunity : topLevelCommunities) { - processCommunity(context, topLevelCommunity); - } - } else { - //otherwise, just find every item and process - Iterator itemIterator = itemService.findAll(context); - while (itemIterator.hasNext() && processed < max2Process) { - processItem(context, itemIterator.next()); - } - } - } /** * Set IIIF canvas dimensions on all IIIF items in a community and its @@ -162,10 +135,7 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic @Override public void processItem(Context context, Item item) throws Exception { if (!inSkipList(item.getHandle())) { - boolean isIIIFItem = item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') - .contentEquals(METADATA_IIIF_ENABLED)) - .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")); + boolean isIIIFItem = Utils.isIIIFItem(item); if (isIIIFItem) { if (processItemBundles(context, item)) { ++processed; @@ -184,7 +154,7 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic * @throws Exception */ private boolean processItemBundles(Context context, Item item) throws Exception { - List bundles = getIIIFBundles(item); + List bundles = Utils.getIIIFBundles(item); boolean done = false; for (Bundle bundle : bundles) { List bitstreams = bundle.getBitstreams(); @@ -192,14 +162,22 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic done |= processBitstream(context, bit); } } - itemService.update(context, item); + if (done) { + // update the item + itemService.update(context, item); + if (!isQuiet) { + System.out.println("Updated canvas metadata for item: " + item.getID()); + } + } return done; } /** - * Sets the IIIF height and width metadata for all images. If width metadata already exists, - * the bitstream is processed only if forceProcessing is true. + * Gets image height and width for the bitstream. For jp2 images, height and width are + * obtained from the IIIF image server. For other formats supported by ImageIO these values + * are read from the actual DSpace bitstream content. If bitstream width metadata already exists, + * the bitstream is processed when forceProcessing is true. * @param context * @param bitstream * @return @@ -214,6 +192,9 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic .filter(m -> m.getMetadataField().toString('.') .contentEquals(IIIF_WIDTH_METADATA)).findFirst(); if (op.isEmpty() || forceProcessing) { + if (forceProcessing && !isQuiet) { + System.out.println("Force processing for bitstream: " + bitstream.getID()); + } int[] dims; if (isUnsupported) { dims = iiifApiQuery.getImageDimensions(bitstream); @@ -223,6 +204,7 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic } if (dims != null) { processed = setBitstreamMetadata(context, bitstream, dims); + // update the bitstream bitstreamService.update(context, bitstream); } } @@ -230,6 +212,13 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic return processed; } + /** + * Sets bitstream metadata for "iiif.image.width" and "iiif.image.height". + * @param context + * @param bitstream + * @param dims + * @return + */ private boolean setBitstreamMetadata(Context context, Bitstream bitstream, int[] dims) { try { dSpaceObjectService.clearMetadata(context, bitstream, "iiif", @@ -240,6 +229,9 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic "image", "height", Item.ANY); dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", "image", "height", Item.ANY, String.valueOf(dims[1])); + if (!isQuiet) { + System.out.println("Added IIIF canvas metadata to bitstream: " + bitstream.getID()); + } return true; } catch (SQLException e) { System.out.println("Unable to update metadata: " + e.getMessage()); @@ -247,6 +239,11 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic } } + /** + * Tests whether the identifier is in the skip list. + * @param identifier + * @return + */ private boolean inSkipList(String identifier) { if (skipList != null && skipList.contains(identifier)) { if (!isQuiet) { @@ -258,53 +255,4 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic } } - /** - * This method returns the bundles holding IIIF resources if any. - * If there is no IIIF content available an empty bundle list is returned. - * @param item the DSpace item - * - * @return list of DSpace bundles with IIIF content - */ - private List getIIIFBundles(Item item) { - boolean iiif = isIIIFEnabled(item); - List bundles = new ArrayList<>(); - if (iiif) { - bundles = item.getBundles().stream().filter(b -> isIIIFBundle(b)).collect(Collectors.toList()); - } - return bundles; - } - - /** - * This method verify if the IIIF feature is enabled on the item or parent collection. - * - * @param item the dspace item - * @return true if the item supports IIIF - */ - private boolean isIIIFEnabled(Item item) { - return item.getOwningCollection().getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")) - || item.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")); - } - - /** - * Utility method to check is a bundle can contain bitstreams to use as IIIF - * resources - * - * @param b the DSpace bundle to check - * @return true if the bundle can contain bitstreams to use as IIIF resources - */ - private boolean isIIIFBundle(Bundle b) { - return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, - Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", - "BRANDED_PREVIEW", "TEXT", OTHER_CONTENT_BUNDLE) - && b.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); - } - } diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java index f8651d8116..9cc6e5677d 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java @@ -7,6 +7,8 @@ */ package org.dspace.app.canvasdimension; +import static org.dspace.app.canvasdimension.Util.checkDimensions; + import java.awt.image.BufferedImage; import java.io.InputStream; import javax.imageio.ImageIO; @@ -15,6 +17,12 @@ public class ImageDimensionReader { private ImageDimensionReader() {} + /** + * Uses ImageIO to read height and width dimensions. + * @param image inputstream for dspace image + * @return image dimensions + * @throws Exception + */ public static int[] getImageDimensions(InputStream image) throws Exception { int[] dims = new int[2]; BufferedImage buf = ImageIO.read(image); @@ -23,8 +31,9 @@ public class ImageDimensionReader { if (width > 0 && height > 0) { dims[0] = width; dims[1] = height; - return dims; + return checkDimensions(dims); } return null; } + } diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java new file mode 100644 index 0000000000..5620585f1c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.canvasdimension; + +public class Util { + + private Util() {} + + /** + * IIIF Presentation API version 2.1.1: + * If the largest image’s dimensions are less than 1200 pixels on either edge, then + * the canvas’s dimensions SHOULD be double those of the image. + * @param dims + * @return + */ + public static int[] checkDimensions(int[] dims) { + if (dims[0] < 1200 || dims[1] < 1200) { + dims[0] = dims[0] * 2; + dims[1] = dims[1] * 2; + } + return dims; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java index 6cdac16cf0..ab2d385367 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.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.canvasdimension.factory; import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java index 19f31f145b..da69a33990 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.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.canvasdimension.factory; import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java index 87277905c4..0a85a136e4 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java @@ -1,9 +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.app.canvasdimension.service; import org.dspace.content.Bitstream; public interface IIIFApiQueryService { - public int[] getImageDimensions(Bitstream bitstream); + int[] getImageDimensions(Bitstream bitstream); } diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java index 518634edd6..43657c0dff 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.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.canvasdimension.service; import java.util.List; @@ -9,22 +16,18 @@ import org.dspace.core.Context; public interface IIIFCanvasDimensionService { - public void processSite(Context context) throws Exception; + void processCommunity(Context context, Community community) throws Exception; - public void processCommunity(Context context, Community community) throws Exception; + void processCollection(Context context, Collection collection) throws Exception; - public void processCollection(Context context, Collection collection) throws Exception; + void processItem(Context context, Item item) throws Exception; - public void processItem(Context context, Item item) throws Exception; + void setForceProcessing(boolean force); - public void setForceProcessing(boolean force); - - public void setIsQuiet(boolean quiet); - - public void setMax2Process(int max2Process); - - public void setSkipList(List skipList); + void setIsQuiet(boolean quiet); + void setMax2Process(int max2Process); + void setSkipList(List skipList); } diff --git a/dspace-api/src/main/java/org/dspace/iiif/Utils.java b/dspace-api/src/main/java/org/dspace/iiif/Utils.java new file mode 100644 index 0000000000..eaadd256a3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/iiif/Utils.java @@ -0,0 +1,85 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.iiif; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.license.CreativeCommonsServiceImpl; + +public class Utils { + + // metadata used to enable the iiif features on the item + public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; + private static final String IIIF_WIDTH_METADATA = "iiif.image.width"; + // The DSpace bundle for other content related to item. + protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; + + private Utils() {} + + public static boolean isIIIFItem(Item item) { + return item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') + .contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")); + } + + /** + * This method returns the bundles holding IIIF resources if any. + * If there is no IIIF content available an empty bundle list is returned. + * @param item the DSpace item + * + * @return list of DSpace bundles with IIIF content + */ + public static List getIIIFBundles(Item item) { + boolean iiif = isIIIFEnabled(item); + List bundles = new ArrayList<>(); + if (iiif) { + bundles = item.getBundles().stream().filter(Utils::isIIIFBundle).collect(Collectors.toList()); + } + return bundles; + } + + /** + * This method verify if the IIIF feature is enabled on the item or parent collection. + * + * @param item the dspace item + * @return true if the item supports IIIF + */ + public static boolean isIIIFEnabled(Item item) { + return item.getOwningCollection().getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")) + || item.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || + m.getValue().equalsIgnoreCase("yes")); + } + + /** + * Utility method to check is a bundle can contain bitstreams to use as IIIF + * resources + * + * @param b the DSpace bundle to check + * @return true if the bundle can contain bitstreams to use as IIIF resources + */ + public static boolean isIIIFBundle(Bundle b) { + return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, + Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", + "BRANDED_PREVIEW", "TEXT", OTHER_CONTENT_BUNDLE) + && b.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) + .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java b/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java new file mode 100644 index 0000000000..d321761ecf --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java @@ -0,0 +1,511 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.canvasdimensions; + +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.junit.Before; +import org.junit.Test; + +public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { + + protected Community child1; + protected Community child2; + protected Collection col1; + protected Collection col2; + protected Collection col3; + protected Item iiifItem; + protected Item iiifItem2; + protected Item iiifItem3; + protected Bitstream bitstream; + protected Bitstream bitstream2; + + private final static String METADATA_IIIF_HEIGHT = "iiif.image.height"; + private final static String METADATA_IIIF_WIDTH = "iiif.image.width"; + + @Before + public void setup() throws IOException { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + // second sub-community + child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community 2") + .build(); + + col2 = CollectionBuilder.createCollection(context, child2).withName("Collection 2").build(); + + context.restoreAuthSystemState(); + } + + @Test + public void processItemNoForce() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .build(); + context.restoreAuthSystemState(); + + String handle = iiifItem.getHandle(); + execCanvasScript(handle); + // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + + } + + @Test + public void processCollectionNoForce() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .build(); + context.restoreAuthSystemState(); + + String handle = col1.getHandle(); + execCanvasScript(handle); + // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + + } + + @Test + public void processSubCommunityNoForce() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .build(); + context.restoreAuthSystemState(); + + String handle = child1.getHandle(); + execCanvasScript(handle); + // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + + } + + @Test + public void processParentCommunityNoForce() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .build(); + context.restoreAuthSystemState(); + + String handle = parentCommunity.getHandle(); + execCanvasScript(handle); + // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + + } + + @Test + public void processParentCommunityMultipleSubsNoForce() throws Exception { + context.turnOffAuthorisationSystem(); + // Create new Items + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + iiifItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test Item2") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstreams (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .build(); + + input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream2 = BitstreamBuilder + .createBitstream(context, iiifItem2, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .build(); + + context.restoreAuthSystemState(); + + String handle = parentCommunity.getHandle(); + execCanvasScript(handle); + // All bitstreams should be updated with canvas metadata. + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + assertTrue(bitstream2.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream2.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + + } + + @Test + public void processItemWithForce() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + context.restoreAuthSystemState(); + + String handle = iiifItem.getHandle(); + execCanvasScriptForceOption(handle); + // The existing metadata should be updated + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + + } + + @Test + public void processItemWithExistingMetadata() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + context.restoreAuthSystemState(); + + String handle = iiifItem.getHandle(); + execCanvasScript(handle); + // The existing canvas metadata should be unchanged + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + + } + + @Test + public void processParentCommunityWithMaximum() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + // Second item so we can test max2process + iiifItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream2 = BitstreamBuilder + .createBitstream(context, iiifItem2, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + + context.restoreAuthSystemState(); + + String handle = parentCommunity.getHandle(); + + execCanvasScriptWithMaxRecsOne(handle); + // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + // Second bitstream should be unchanged + assertTrue(bitstream2.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + assertTrue(bitstream2.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + + } + + @Test + public void processParentCommunityWithMultipleSkip() throws Exception { + context.turnOffAuthorisationSystem(); + col3 = CollectionBuilder.createCollection(context, child1).withName("Collection 3").build(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + // Second item so we can test max2process + iiifItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + iiifItem3 = ItemBuilder.createItem(context, col3) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream2 = BitstreamBuilder + .createBitstream(context, iiifItem2, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + + input = this.getClass().getResourceAsStream("cat.jpg"); + Bitstream bitstream3 = BitstreamBuilder + .createBitstream(context, iiifItem3, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + + context.restoreAuthSystemState(); + + String handle = parentCommunity.getHandle(); + + execCanvasScriptWithSkipList(handle, col2.getID().toString() + ", " + col3.getID().toString()); + // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + // Second bitstream should be unchanged because its within a skipped collection + assertTrue(bitstream2.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + assertTrue(bitstream2.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + // Third bitstream should be unchanged because its within a skipped collection + assertTrue(bitstream3.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + assertTrue(bitstream3.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + + } + + @Test + public void processParentCommunityWithSingleSkip() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + // Second item so we can test max2process + iiifItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream2 = BitstreamBuilder + .createBitstream(context, iiifItem2, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + + context.restoreAuthSystemState(); + + String handle = parentCommunity.getHandle(); + + execCanvasScriptWithSkipList(handle, col2.getID().toString()); + // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + // Second bitstream should be unchanged because its within a skipped collection + assertTrue(bitstream2.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + assertTrue(bitstream2.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("100"))); + + } + + private void execCanvasScript(String handle) throws Exception { + runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle); + } + + private void execCanvasScriptForceOption(String handle) throws Exception { + runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-f"); + } + + private void execCanvasScriptWithMaxRecsOne(String handle) throws Exception { + runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-m", "1", "-f"); + } + + private void execCanvasScriptWithSkipList(String handle, String skip) throws Exception { + runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-s", skip, "-f"); + } + +} diff --git a/dspace-api/src/test/resources/org/dspace/app/canvasdimensions/cat.jpg b/dspace-api/src/test/resources/org/dspace/app/canvasdimensions/cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7ac530cf24c981d4d936f8b526dd8af969b570b6 GIT binary patch literal 36814 zcmeFZbzD`?(>Q)E-AIa{fTSYb-6<&zDs{QQ^%mS9MJxnCKu|G|kQ5LQBot5#1jVEq zL>i=|B=7HBLVdqK^}IgM>-qij^(g1e&dkp4&d%;RcV>5q9mE0FGJ`<47XX->17ZLG z&;ZmBR)8FYAmBd$!VOSv!2sX^;rR)lf*ji6Ap?120Pqg*f`3?u>=w)p!dD~U0L4z; zOz`;`pj$r+0HS+#pE{lx4;a6d4+f9H`Cxqc^=uUR4>`cmo|pjKVL(<|MqX7~Syfh^ zUq(q)T1izw8UV=CAiwICMz$*-BA!OR3j=S!vPc&dgnWmW>i2d)cz?A6BJz7X$RN8i zAd+N1WwV5Bivx_oC#aF_C$WP#1u$&2z*Wy2fDoqvrmYm;^6Z3^X(}v@{H~ zv$AzEN9DIr@ISYgsd4nW8#D5lYq!^4Rl@dep)RHnRhBP@Dp1ddUS5{cE(=2#doTY>iP`0KZ6ixcMh@Nq5jS%x>f1+`SpYIJu(lLib)u%CRM@J* zK^ayGum*ktY?QL+;@L?xcwyZ*D<|YJfIXj}XTt%-$kt#qfqVTc_tVG6MPXjwe zJnZYwD*Wpji2kYqu?t`z-zt*@&;mxLczlyKtnKCGB{Zq*+gvMXYvvI9X-~z9C-gS@ z^t4rqSRQFPlWxn?7{Ewh;GYU`S6AdrUy`0X()ujtLnwC%byG;(*R0sru_t-YR{QlD z6t%JO90?O(z7vTljlUN|JJ=AGkkB@0k#f{@FV7?u&kUE7_cy1i@?gbOM2bSEp;38N z=(8ocPSlOfYu?#qufDu!WMq05Dd=xda&_L~S~{!s^YgbNoR@X%tzO?9L(Nm(` z|NZQG0J-cz{iiQKT+8t&37%DC3>9{HfJ9hLy>C`p++ZJUgrEA{Pib zPo0n;lCcO)oN9WG7Yn(|t;3q>+ApiuV}NwvMS{el(9SJ$7i3NsShvY^iE5 zTK$ObD#9!sDT7z6yKOiH%3XtM1rQWNE`I+|5YV*g{QaJR#FN))=%C)(OPrg&ngC3z6wVlaspFwQOZa^a3kX=WN;)1h#PTHOLUD;VNwd~Pg?Yt-4a@2kt2 zxX{y&qP@_*@1xJ^tHwJP^|Ir1zpn|ns09WhrcR04Ey6qFtt8VMr*Y1q1Dr`WUcepsJ^et%xm3SH2m6q{NBYQsmG6(Xi69M zd=?uVd-44E^3)1jG|6QyrnK>{%CvV^^H&2cmbo;4dMO9*(T&6 zN|hn7sUK3-bP>rm@HRio12?&>x6X&%aoeUI&Jsl&x!309OJ zA5H?|&5JkFJ&G55lWfFuUzgjPJ;~%%q*Uj7pur}ynXGbyhqkd$O>|SLrTeLXk?*~I z7=z}@rDl`jQG}z=t<)Dmu5G-TJ=3}I*Lz=9>^TCx@=BZByg#X`udc9uvfd>l+pU7O zyz=hdc;OHHs679gGcyZ)6WwJ&eG`SyXCG&mW_&^nv(omsYIWMp^Qa3>e2R3qzt}0F zVE7IJUuN;`onu6FHE#wty&ESq8N@*Eo}d(u@h>f&ytSC8bab`*H#w zv&PptuFb8w45^+X+``)`sluk`BKAduK~Gh8kt3J^s-*xi74#c6CB&V!c#601{p*68 zMe{^J#`fcQ#TA6&2^SPj_!;phw{{xiOZ*hOntnuiQ)3 zC?Q{At~VF2j)&SjSL)YSC>=fq6-U%%Q%Ae5#i(^W<$Vm7^6P4pR=>%!sMG4;rGbiY zo;g;|_i+!^i?!|m$&!sv{D#S`cNO%PC0yLD@vBkUk38wlhvHSyw)+(yt2kjEm7Y)M zZdawd8OjCc+*s58d@*>EaAy2!5D{>_6PHmmR?8uchIu|I;ra1gH!79bG}Mpi^i1&l zE@}ko@_Kep6_=axlU|T%&SokH*J4?o0}*~xF=0`L*y+3j zH&DwbMpcGxR`q-}@gHye*fUHwLr^TeCLv94k#1OhsAD7~Ul5sW1fQaNT2=lqf$6j7 zlFZyup|0rGcDrPqk#6ey6CGV0F5X=fYXPBrS>K#anoewh7-d}&MM^D2j%my2J^>oLZ zVzbH+DXsFR()=4pmEUFZDuRMGnot`lpNc5DlG$E0e+ z-h~xZdAuLraP;h5GMk@pze;{$lY4H#@;f-`TAefY{r2o#@?Mt-am1@T?W}F9VUOz~ zF^$d&D$6x3UK9ID&wu}_bo=Uz>DV{S%!ZnSQA=fL;xYM3msi3UjWv1fsqaBuynBLF zKc>oONyyV(Ayl~}r8hP_fhu|8g+$dd1^F~0}M?cjSia4&shasx&b?P!y9_e zH*wjpr0dHbuhW_bZJ7o2ULMgSXifGCrXMCYf1Nnferveeu!Q-07{O3mgt{Cb$9k4` zT!_&K2s*6UZbnlIHdZ|>A#$|BXIfb>S-sV;I{9>!!0Oo$ zj}w8suI$b1=RZDI<~M3=nd4TpQ#;X|K>>^h@YB+iYka%~jZXUN7IvU}Piem-<#gbZ z+(~WGzEO*j;+h|#-;Y>cv`^@L{)S*F9#H;-vYeIs^9l-#Z4#S{Yr4UXG=`+%H7m&DhqQa#F)T@b!EbxoNWADl*d2u6Ocrl}r7sp8l!+2Uc~fA$mGP zEICUzdel$bxCOaMj)cHpzS;Yv?)br+szB#n<+x0<>-xz1H#1(50sC)MHm5y(klLS8^Q?kNxP8SE@sc(30;y8d1V-JrngLP#U*D|jeYK32Fs)TM%577 zW2X}*4$_n7xs50*=DhHwtjajqya4U)?%a$&QIen|mvaZ(4d3G_fC@s!vdPDErMLZn z&*~a*5&>e%eEG8z+RK1Ci&K^#uRw_#!5CaOO2#F}8QaUrtbHx6x{~Mz~UGsVdf&#(&Zh>N0UMfb&S!0(X~e7-jf!5+NF>1F*E^dHh{^c zn4k+k(3?%1ITn}7+o&r{_$I~P9hUekZ!~B7P+URXeu@h}^C`CGPI;20{{aEF&HZUs zEnMBtnP?Ukmd^9Pluw)(nH$RPc2@rULx?>gkKSy)`=qc-9uY7L*0WNu2w6r2CLHFo{TPCci3@}y^z!ig0AHu|nw%MXKi<)ERr~ploz-gfjo{0g z%3RxsU0*|*k5LWF%xGmlKtUsu4=nUFY}AZy$a|?KNwS}9^hEb`UI50%H)hruB_-_4 zyQa%J85U)F4&+~W2ayJPZA!S8SM5Wd*1S2sA(*E&6$b5pmZYZn()IYc)`wI58Qtqe z*-zfPn<8>wUwl&7T8LLkTQh8D?p@U+0`Hgef3(XrL3#BG;F>X+#uRI(HsqBsPit;P z-eGV_8A+@@SAYIg{=>-Ldk!*U&^dVMmE|WflGh%;KSKmY1%o-xOe9WSN+q_=n>RtP z77k9HsCjrq`^nO$cl;BoFOvv(H!l%^yG37uLo8;F5>}5Bfmtofd0W}e zy9yeD6DZ(?pdJx8bHgi&>WkUlD)Zy($Ho>yaeUrih(K1{s_WgNN6ib9o6z{Rxq0&w z?G3MR7fk#PJ{yG$&ZpvT&_A4JTs(3=iQALw_SE>?lkS_xvR|zCH_gZpfnb%@;sJ2v1O|}WSt@dDPLnL1>&3gE~d)*%hJtY)KvlR6$yylnhU+g zLaE9eRrz_s?DAp8s?G}hIB)D2I35byt8f*lWOWg(8;`mY^BU=q4?Q|jSKv5fjyT%5 zzEw4zCmNFVZyq&6V@j_&_8u(qT{S;9wPZPeKVS|yom?5{8y3+0XqhAS+WP12at*0~ zdkc@(XKaXofl#)DctQC$NrhE(K(4!FC*OOe{iyC|IYi*%M(v3s4=SU+bGOSZt?~9g zSKsrcy<;Y8I<#PsrMv3eG8+7-CjvAvv8y~mS8^TnL-B z9JRJ_dy;iUs%!?W*VBM%%=!RN)QWi2y{gr;p*19N*RA(GBgHBd@qUcPXE?RGw>uG? z>h4)T5_)(cn{T0fGhjNb4U{ZlW^sI>$|$#O)Uo0Gr zwda7V#x4IFJj)OKOp8LY4vjF$}zPw=($I0XfX41g7Y1u)=0FMuDg0bl?gAOOB3 zQ2?FqPi`=^B}NyCNAJ{#!Gi!t;^F8mkszIRi(Aj!$#h#AY9tY465#Dui&=kbv868_ zjzQx<`d$*Jo-ZD~+X%3c9@w7=Yj2$S&x9Tpt@kT|#{Wu~c_48=DHh&%|DOp16jJ|Z z0@UL#(YjEC_l_dAwFFoj>FR;CAtzPI+LNE(6GQOO#(W_qw=N?r`X6w*$baP1#d_M> zq49=-)<{y{{=9UNp8Wp|Z;eCZxA0bhNNww%>~#JxC?12=cgMSvTCi0~D{m{@u2qm= z5;OQDRU0sad2NgNTd}sFgY%yiYXe1YS7}C(7 z@o>k%ew$(_0XqN(&K+2g2DaJ*LqL1t{x-0nco~pJ>K}k)jX%2_O#jVf0Nr1v^@hZ6 z{nK(F;$V^rhlE36ID4cS$@U?CDTi{42BKNPKkBW7iT-b8QF~)C1mC})s4!T#H~ioGSJ1f&|Gm|hRiV4b_AAI~@x1XGVMgC=O z-|CvbV$5;gf1UrS+>v-&ckjP&F+pKSq%ABEZ-O(nH8VYlB!|COEI!&?u7#9)6@F3YxjU8A1_FbiOT zL6dY%i^uqaZ%!QS_bG)A3C=zLfMxL5nydZ+PY-sn&)?X$M#~{mIpCWS90ppmTk!VC zAcd8-EqH5|pdfJoEJ@&vG%u%te+Ur#0swD77!2HSfq|Gmfto*onm>V>KY^M*fto*o znm>V>KY^M*fto*onm>V>KY^M*fto*onm>V>KY^M*ftvrzK+U$_<{;>q0YDEW=$s)r zYM22Xz!MC3cz_N|elYyu1HyRl54Qz@4qAZiKXC8^dSIwTktA@d1m-jl1gJPs7+;dJ zpX4DW6-2tI!M|-7yzhEwxVC=c)Z?UVkUGJ?s&LA>=?nF6yMkieUd_&J5iIZ z8HyD4{5?$yjsBhngSn{P@BwfPi4WQ#lBP2{4_xVe_h}{kc+oI^;eigKkOA&NI2}(< z9VA`{kH^A22>7i|CDnlOCq@s9M($LNWW5Y}NDL091BSNTy?^!zjR6{p@dU@g4kO41 zu#&9G#@!#p{@c3HfxJw57~i13HiT4(efk7nFkT4ih9rv=64izIfD(UpKZ%Lc7>`2k z()BPXU(n{z_}u^|)lQAbcAi}S;YrdA>y~D12p(P-q~~8&dr$*#00p=M-r(2G*3tlg zg;Xn2so*I0fd9h?*tIB<<`{pmn!7`HJB#TT*3QDjaTooY+?{PxB+8a;Y_DlC2P1)S zUvMOYn)%reT-*Bom)fA+X+31;$-eX4)ehTEdRJ@zN?Rl??cECH{>!3vMF2Y@cSY~t zN&karP$bK)RsYoeZlg&QGjKhG0k|XwjUz2>*;UjYBa9h18SQq#uX%3IRxg^n`u2xm>W$MJhIjV_*Vk;f{ol?5 zqy-W5;B2Hvz=10iOsschP=h!FM-#){G!>Y3G`HFRZBD4cu%_$1qkes8Z?p$1=u;4VA}KpbEZ@WJDKRi&iRI7xTXrb$UC z1|=2f?kgoDDJ=!4YXyR4fPmroJz${KX$Z_z)d=vzJv9Uz6wIZ~ef3~IaN}Ss%qG~v z78;Czs(1=$Y4WQFss^HbQK0Vm15rpcPBlEpkg%;G4eC{f)ch@>zPlS# ze`fiM=%3`R&e+Was@B~6pJ&+ZHx%k;5jeboKiG_a*ULE4EF@(O!)@8Hfj>B%2<{e! z!&}3C>GLnO+b!&`*+KTL%)Y96SeQGwyv-JTZ|wH+uXUAw*^o;rY=<8Pu7-nY2#|K@ zN=Pe9$jaNwfZeUEDi0zTVJIb0^sQL zhDn|Bg?S6`|Lgcl@tUTnzynxWtg(#6V9`MnzFkLRL~{tG?#uswQY0 z-W?5vndoZ>fQ^-e!#!2y4CVEt^_BJXRb+Jyv?^Xg-u z1d{FS=Jh1yRr#Og-CpYCj{L7=kVZ1e*i?<-;GBpJ+8Jv$FuxrL3FqIkFI9Ia=?$kL z044P~%u`?o4Zf%TvDN>v=G!6zz!i%B))2PYa2PLqfIAkZ;|*%)KiQeozp6LR-Tyz- zTIWCM`(M?1%O<5|c5U*%sP&FANxxmZVc^s&CGfv!hr$1<9SYL_|Fi>&L6er4{pu56 zcP#j!iwECx8UkKe42pkyjQXOz`SF;Y#MXfQm#P83q`~jBpQib{z@Me@|97JQv-SMP zk^XP3=l?+KQlwYh_CS{U`xxI&>u>AJkfiLbw}9$D9nJp(djekxs-FMV9JXEV_7cwj zatfwJ`15N;bN8#_&-0H4{%GKj2L5Q^j|To|;Qt{F{PK6e(BPML z0O-vier0e3f8x1XSz8*Km>Fz^nHh{h9~*`Y^z@^^9cQH9e2z}e{L~-81FC4jeL>(} z7}iXkXaCB_dt+fGLlb@9Sc?vgt?oK!=#bvpH6etDL4812 z8pJbTZLRe{SRHgkF?jz5d;A9D!LSR+3y_@fTiZ7H4?z#}gAN;Ieq&ew5{z(3SbtZZjzt!1@nAy0_5Vw|`6gjql%qq`uvz&mY_BGYIGVU$|d6f8j8o z?@bxpXGVVY7tZ50091qn09V&9oKPC*^ScND<)40Q4=2f9cKkIY2NoO#l_V+j=k*^M zescb6;J5N5N#*@?Sn%s_@ABa%Sv3^gwL`%2<4CSxeu=*u@qeB1w`%=X4{`8!0t^cS zoh1DBpp}8{Yp}aPH#W($zz;|N#jWz2g#R+zZxTr0wq1h&TjnOf?kWlF`NR&8PrU~y z*ysWB6Dc4C^0VK}XzjtJB>>>SH@a>2APmyC?ti(F#eqpO9DHl%7@%iu%MT@B{kK^t zNDoSIcOo;u4(tPuxDx~p0TO^LpaiG^+JFIY6tDzr0Vlu(fP#xxP~iT%0N^xm4!8tF z16P3?z#SkJ$OLkMLZAew0IGo(KppT7JPNNB=mx$3!@wjk3oHR^;4y5}5Jm_aWFLeN zA`CeKk%cHjG$95MGl(_B3E~FvhM*w?$Z5z0NHin?atCq`k_#z@Jb}D`yoEGEIw1p) zamaVbDj7K$0~tHn0WuLXDKcd;9WqlgTe6d6-ei7c!DJW7V##iirIS4%D<^wF_KvKT z>*&=vK9z8iH`9X3Ca%FNoa!Yb&axZc$`5E#k@s*FPE@C;La44#B~ukqJ*R4<8l+mJrl#IU zeS}(r+LGFx8c%(R`Zo0g>Kf`M>S5|1H1srlG_o{?G|n_=n)5V?G z&GV z*vI&TiH%8;$%M(1DU2zJshp{aX_}dqS(sUe*@ZcnIg$Ah^C#wSduaEF?9tofzUR!I zq&-jewC`DDVPlbDv1CE9Tw%#$dCM}!O3f<5YQXBrdXY7Q^%d(N8#$XGn;sjK?E+gS z+iSLAb}DvJb`$nf?9uE6>>t@@IaoR5IqW$CIg&V@a(v+==M>>I;Y4!Aah7nlajxz? zuvcd-B2qPU8HwCvC_c87O?iB9V z+~0WEdDM8IJW)I)JYDd8iwAfQ7#;9EaQnck1K)T#d9`^@@m}YB z#yif(#;3^#=ey2V!#Bat!LP%Q8~M5r>lxe-24WTnWHl2Wh!O9%ZkZD zWs_uEse>7k< zurs)0&}S%M=xvy9IBTR}6k_z;n9|tPINrG3Tu@JUETU1yAmL`_hEPJg)t^BO2j!_-6I+k>7#9GGsv~|4=n~keY zp3Rc2u5Fxcx7{H-g567dX8RNNx%SHr`VI*WUmT?zLml5aaXZ1C9y?Pz+dF4FFCI5I ze&hJ?3564vPPCmAJsEiNtqZpc(&ecuv#Y!7BR5Jn2e&-8b$3hmboWIM6OX$dvrs+g zP3WYjw&!)vahL`y0XFKT;g#Su=B?p<&3oKO%O}z28(a^52R?Vo_*CktWrQUn2SG$S zAd65mC^ys-G%Ff`uEp?T0x^xgM|>~&e(_WFyWuyBHN|G*Ah?sb$9Q(|W9mKO5Fwl} z;IHYQpF2GesuOze4CIW*nU`UL zVHd-O&l;V*e~#`P;@pSx(&rP-FI;fAPFC7h^()XT4KXq?w_=E~Ua=qJ6ys8^Qe8z~ZHw26zn{R85S%c0 z&HP&Vb^hy7*B5TM-guiRpO|`+?k3^pms@7H9^V$aef9SG9iKa`NxDfzclY1De0Mq7 zGr1{6JEbu7Kx%aAY8pJPK|p+w=mBF>`dhrmO^!---+a;gtjCq3nO8uyIu*~MqXn&6tn z=f2Osz3_f9_|o-d_bbO&Ew#sLKfX47{ie>a?p3``ea#zG13r z>qK_Wb@_L#b)W5_?1}2#)0^10zc0Q2aDUMk#V=0>^atL5wfWjT2pyap!VRqtUmRf^ zNgU-H%^i~+s~$HT|2%PgVtCSba_!rtDVC|b(}$)@XS8SD&pOTy&td0?->=N?o6lL0 zUwFA_wb-|WT3TC<`myiF{T1bv`c;S3(Y1he>h)V2hc_xWO*eapC@@<8>tI*Xx4`zn zu2d8h6qFQHl$2DoRNx;y4HXp)Jp&ybJslmx9)_)J=dqpm`yB$l;}{tjx%TYg`rjPv z`UWCS4gpU>{dKUb2(cCbw?mVWkwbp_(jgs^3L&EgXu!jBNe8}?8nSaiwoY~31CW8| zxsroT0*`ZrfNRu1G{8bS&{c=>n7aTQ6?menE_k4;wFmp`i$?5uK{@Jyg!B@QmwG}r zb4_2N7c%69X;`*IlWmo~T?T}O>>#-es}9B1p|G;|{(5NYXtJ=@je|?=Y`J^e<C z2FB8K;K6dsyyEKNYX?(Ws4^9*?o)_Zn@nY07Sqc0P#FD|cUdj!a>cW{&1O3bW@Wus zF+W1YY_A|_m11a$$u3);QlxO4{)d{W2KqEm8Iy3YW_HQw*qEO{~7`wPI#G z(`C3X99Y$Sq)*|(zdv`tn=D1h>yk!VIA40JcGOk#5z3f?O3h6%%&E+j`ONRj$Llvt z*QN{^!6QxY;j_vG9oTYd+vC172NfuLH$BTAP{=c=z1iSvsY><0ae|j87Jn}@SYBdu zLZz)ouIN4yD5uLEKW)3;zh}+lDg4_CbnlTs{DYRgcXjxiYsavrh1cH>U7bs7QvOK8 zJK2}mLznC1YA41 zpily1HDBRKKbB_7&wA)t)d^;}LZ%q9j_zECE!%Y4WlLI}{XX4yYI|)1P8K*=Y5q9# zG;veakl2`kpF@%%F1HmC=6#Gnjlr5&3@??M+H=#nD>Zw$bHs6zH8(oo@>3d*&wk2j z>hGQv7~|@lV03sieEme)ZJM%`=oXJG4^JcVilda`b9BLXQTv^l+mANJsA3$SzkPF) zb6@*>;5X|NUQOB#G1u4is%<}J&DIp8SgU>Yk)I; zW`Q@NuT}qXd!7(H$YVWb;(~Ws6TY<1LLVhbH!>UJPaDjN{dBkTieMKAG0_ zE`b6iUs(K_t5>M-#W5pRro-gM&>D;Y{n6RHr$qpXP?f6zV;^KBev$JDK}btB37foWK0^8-rbPt*MAPjd<5oRr6y`QOTJ~U;r z|6S{1u*%>7+ln$}OIDO0XW4@g%F^5zsUr?c?^;)QlNJpXkI3=fY&q%H5RL>-iF|vg zhRR6r2O`Y%(PY5v$nxql@FYIJ1>5k}58+E?F;x*$MTSN<9=uR!A-~r}{cK@Kg4=^G z6|QB#-dJ!=-1$OUdGf;LDmgRB5XUo*Y~UHrqH;jp@?6N{Si2kr<=Vgk6cM;^=4ssx zviCFO2|3zm?Io9tugR#(^4Ai9IGO~DK8Kl{)7N<#!sO63Oq*+{-eVDQ#O56S?q=Hn z2e!hzGZ|mcmkq=j->#J_*De`P@*Q>w-sfhr$E%WFP!jKq}yA@{I&?QV6*1Yl-&IOex>Yp&0Z%S?BkWLyks4~uN8?XU*8lP zd8A%%R`s<&Gi7sQwc_5(VuExsg2lOUWz@4y?err^5fM1%Tsqp{ve~h`+S4Dmf{zU+ z0t-s_Dr5BnJ56ph4PQU&deAQ28uj{fD)ZMeyk*De_mRoP%Gh&Fl|xckzBm&VqdSNot)#{zCwS#qFMGMN=`(i_MqJNCrTf;+h4TYJAyoZ81 zeMck;q}2F4phH5BKbwE_8N8a(JLp%0iLP!~S(6>am8`<=wRakb#x|+x@LN-4o$IVP zU=(?7HTTQDsJbhtSEd1F7W?+4$Q(YSEzM_;do^18%<)4%{PSG9q-=a7uD%r+p*B1o z=YRcNAXZ;i_1Rg&3AF&(F|kZ?BGB-v=2LO^5OR3#TQ~YZsard-<$iuF2FJrE9jif>SN|@`z{HFnvcInS2W40qD_HEfXCJ7Tf?s7 zZ`2ekRuP)n?;Q@fpzA-LG#9P=@Me7e^&=%t!wCL8CQO&=FO$38oNZ@Zsn1vcurAhn z>QI7)QFz=Hh;L4Ob4`pvRO`{x>$kqYmm4#ANPdp8*ZC~2z%zLvJt*Q-9_ueI{a2;s6yq_&pxxcbxO=_I+(}a{2jP-UXM#N4S zKYwCf;Fan36?i`^OXKvXS~N}sJa2Kx^V6ZQ4HKse90PtKA-OKGI0!gx%^H4wbwfb# z0Msair4#WzN8@^^fNGf4(1%jX8{)5-l6voUteFg(!r$_9J++hzxD%@@h@PiavSw_# zu>2ZWwqzriJ#H=ZM|@S{m#LBwo#B?VQzG=x?kOF0#x+O?-e|SEx?-ji z(iu5-klgNuWZ3%~4~E4?(5AQd1yKh+(=#4Tnw9O?5sb5vU>pIl}P~s@QOC3>qw29e4b4gaBCX09BUqG+xCmCLnH4E~(U>?Zl`VwX9q2Ry7+?>W}U zHUjc47mGFOLxp@LHP1NFFCRmTvgBI0hatVk=V$AS z5@^%~9whN^22Q7m7TY?Zdi7PG1xuex1nHUZQ)ME!^1FHUe+R z=2Z|`w7zmGlKoE6!xxV9wpUKkmR~qZvrnl)Bd7cdlf*{*Yp#t`;v>2ani?hVN?v4N zM1561SpBr@!}M)J=W1_Yz1T$4;G0abS$;sFM$_&?>#d2XC0C1R;U>dULmB+F*x@C6 zo9dJ2^*J%D2?8g1s>$b3`~3O%6mw|A=F9Zh8pM^}aFd zSua)AdUfS)nPQJxq`do>46Q!hS4}0G>(bcpDR!_zG4>G z#~v$B?*_yK($8fzBy3RfQa@sr=(&Jq&~8qB=kO-~iYcd!kPDn=K}q!jf@=A6YmRu{ zxfahG7ygYVGM!2UJaiPH?av zxdpTvJmD57j&JA+DNSqK(9U}^S}MI7u5wx=qohQK3HY4E92#zPk`@qu(A8c04mbTI zw{%DnI?n~4XIqF$vUF5_DN&d+z_^G@^FdvGW9u?OD-@D9%+($6U5j_Jt2ON6Ah9iP zlwOZ(8k}=N4{HQoxQ%`R8b#eTowRJTS}6^PTkT2CyDO^CRuyG~uTWs!5E+RYn&-!# zC`PG`r#+ZCY?^Ic!>yTNssADZ<vdjl|z;I4%L?$vwTiw?D1V@UTE2{H1;r3e;oV1J-k)-_}7!~ zE&2n*lI#xN6-p85@SG>(a7P*51#){8ALlt6UK3U-4sGLnEHqYHu*oDc>-Xw0P5$FC zvYrcBrJ@m`tXEV!V8RZ%b*pcBN}F|qq(W^odn;c)XSHYee1UL%&&A{6MYV0!4M_r8 zEhiE82xf9glIjt;b$Nwv2CRKPysVnyYdMZ=MxY8-%2Xm<;u%)r#WNI>GZ?X6^~}sLNRLv{o)K{X8C3D=#LZ7QBH+9n_LT?>jxSuNb&6p8 z!t_$Py?Ukj$9%-}XSRjUzR!X0xzLY4qSPC~@MWB1x{ICJ@2Zkh6eVDhLU1k#(t7-pq!n5Wq6~oLLffPc)JJecvSN>E0 z^#HLwpCB^}Y3PUf8Mz*na&p7n3b0>am#z{1>~r{1$7IkLJui10Fq8A?O<}}XBoWxS zsM&Z@y|{4jB8|a;msej^sD4Dp}rjX7{Dp0TI}8EU&MO9RjHM+#-v>?gO$HC1Y{=2oO^-dpb8QVSCRCfX^m5tpu~@-0ctR~VF8!f+ zN<>UcmZrP)xf^#Rsv60dX)NfgQ!nQ`eR~s^C)g57{ymw)FP)wXPI09vFegCZWndiZbo>@o74 z)+wLi$XA&J&z$z|uUH@UKQzD-zt?~p(j(6?X%uboVz{37j^p9>u2V163m-re8WT{6 z?(niKJ{OG(ukx=k;tp_%6dM zreXi~SBMak44tAk-z3?`eG^y5uXYc0*1xV>|FI@kno9hfzi2L80o!c&8e4c>d`;`v zo%Q^{w`ttx%))E6Jw=ltS{IpymRfP&Rq?5MBGdOcBIw4hAWFZom4#lv(l)7j$2Sjm zpc%GSn&nO&Ulz${F5Pyh;8nPJ-X%Mpl0}81^+l?qrsEnisq!UiWCDkf%j^6i2G)46k~if-Lg-C}VP zP;08q(CfJdf@_htHq;B>{gW=_9ATua=K30dUL2}R-ViP>e)i@ zLT5z4Z~UfgESHdM;_U~yo(5Fw3WJMf64cfvg4d9kvc2Z-(w^Fi$-RmZQ6Gp8m=gVP zy@MPr^qmOU*v;E`$fdnrsZMj?`RJK}rfAI~M8A!ck|S zwV;g_s6Vbb^$jmfw>QA;>Bdr#9&fOk+~E&{Htx^k>tCV|l|~E5spg+Hd+s{8QkP#jejh*Nk)c3$H(T(UTLyM z&vEz~*ARZ#fIn+$J{1kH5`munqx3bSjW3gAbA`t5X}x-HOPC&AfomKHqu*e>B$l<#|B&EWtj_j#L+gJQp!Qmt&XxI&%cyg>U45)h ziHz{sN-&iO_-dF wBY`0B5AuRG*K!rq85=BF0sC-m!56UQANrg!8`A6_o{P^GbW z(_DCO&Y(QfuvI+pT*w3l&A}0zxLzfeh@nwE(H#LZ8Hc+lb4iO!N(?kxVx$P-Dh3f^ z2XXi8KFj$?)Sr{*kr}-N6`5_2LecO}cYkKoOIm+Uqw%5kLhbQ{R*Q#4hb=81C~urR zc`ICqgF&K*b6|DSCFDjbwz53~=Xs_op?2nyFC7a)BI1aHWlsJwcC%h?z5VUJvkzN* zY6`1HE!D4{i%#ez0w<-e&gqtrRo*}>`)0YpJlzy$%U*BfINz1VcrQmi)45oDCFSd> z*{;fI1b~MwN|;~1r%CVpC1vjtiw#vYm8OolMgL`Lbmbjfg4D|8Fu9>_^F$&r)f%S9 zM{FAD+_3i0zw}iuyYd^_y~=iD;Cp)#qQ!$ZOa8uYK_+*y!7Zj!Az_RfTpp4Sj@4Qh zX1Zu}<+HruwcULA%8KB4+~%3q_4DN6OHsj&KHm5rgHFQcukjLGt5XemA>sy$M^nal zOUDmrT0x;lcx93$W`<`9yuUrFDafaCT~6g?KN1%?J^3P%P=O zTc|w_WnW0p;A;soiLdgD5D@&5AXhQTH=RHD+I;?iLsl5}sa{N@cdgu8Y3Y(zLaD6N z)EAf|3kdzN_z#=dcEy|N`#(3qZYx1xrqM}D@X(-kCRLQZDmWRXW?NoPv_d-hXCTTb zVD{7RI={5 z=ULD9BSV7hLbP14x7Rqcy(g0qG|zcFZv|0^JxY2cBXpo*u)I(8_V~?YSItQ@#fHma z?xr}&RgPnbfF%c|6O~!UP|B-Rh7MHikB6-a&<%~#2agSD(F>leT1h%~-bUuV{T`;w ztBNPk?Y9PwMYmiMO0kW9aIxxu;hn6ig-iqyxTpG{W5e}8n|9=AtqRMhxX$CNp|@My zrx#}wdiMwwKZy1y_(~CSq@zCWoV=17H4{OQ(oDY_Vb>sC-Qjj3cGdrixL?=kQ7jcy zw}Jw@K`?Yb7QnmoG%wiP1p2kkRt{0{`Y@g0hbvLgW^!tguyhesUlHC>!EA;2i^`SRf!6sJ#x&PJiX$4m2~lpwXX3SrJHr1 zMov~AeeHRiiedtl!yS;;F>3nIS(ody5RNZL%+)>o%7_y!aAf^;AkTaS%zSL%mZz+b zNl*HR?i?~Pl(d(zGv;DPd+$*Xy5IgoeImF;wTeRV9} zhoa%((Ttc)w%(4Ad<)4(`3`1DAD+|hDfR&7dOEPv1*_g-qnDf1-}}7ipu6Ak{6`VW zqy}ZAYp+_AQ#?QG$}CK7IA~LU?7ctBQ9`)alTD+g(Za!xHq638k?SH>dWSX$MSb=T z;qRIc2(P+o*VhIxUw0b55rL@S@prT(_!r`$6~$5e1CA%1vh~N69lbKFE&K2eKaw$K z>Q!&ujR)IuU4$EvArgbNHnwMf)uS#}!nip{)A@fgl#=ri=-;;1%ubg92geKQmv zpxFn#nR{v0dwfH~Q`rFy3w^=)&0y1xeE7lYQ`hSst#2u-+;(CaU8EMUwYv3CW;4O> zize!n+EU<~@x|5o4<_Dj14k>@wf#7kPH>O*;N7^IKApDYmLu9)I6&xy8QUeBZ>3OfhYL|~3r6!cY8CX{Dy^rH}hs-N!Y9e;M?yn;AS zzxzVt`G<;=-NDS{gyXaMHx%Netc}WZ<=Yh9;$Db0AAQM+eDkf!{O!PW7DVa;pXAz; z+3BZihsU*uEh95mD3E6(>aL!&bf4Z(R~Tx~JDpAQ!jm?rZ zEqHmZU`?ejv+fR8RI!d1c|cXgvleaod*7=oDj)M?Tt(dDUb9bG5kJUY_(Xy}=@P}+ zuJbA^&Q60i&yEh61bx%t_bV=LD;<0(_HO*@cf+?_@nsa#o`T(3u4aSx7dq9e9b1xh zqf^MTFQnU|ES{#Py;&^GZLYMZAN4%YZE!DoB>ocxFA<1bFa1)hr2qDI@ZpJMN5%Da z@nV*WSGZEhoUHTh?BE&e^;9#b^e{r4+v!+ zy?U>*aZR2ad}4fvQVACOffjB&yK644w2&1t6XL$>aIq%c-L(~P&fNp8w9uRW&(}!X zc@gB}uyR9<2(|snd)v0A#Y&!w&4G24 zFayGXToLUx7nRDfdU(f?AORE*ct7mDWz$IdC)27^q+=T`#c5|JCIfeN4qEM*jfu++N&3zb|JsVO~UfwVdTqa)!~c@b0k# z@b|C#YJVn-4``0jpf@YazSMqAtYv{F;g^k%eFpX95?C$Oz7W*3hKtQvbUp$#E!o3f z+ji2bhK-ejUv;ht;5)@k4H=QuiVGFum((A;r-hoHVGucUCCdU*KZ$vY)9b0$jU)`} z!GSBPk)`(o;WSBbP$pCoUA5e|9^PWFnwpXH$5M+PbrKLvnlBJ<-^RAaSLJ)g>k7q2 z;sMgbU#9go1&K?O+hNH^GNiM~-A~ZL5Zu@*9h^)oT zWMaRQBOIIV?9+@(>=+pU9>#BqEUzooiyHiFkFB-`cM8GmQAcMezN)naV zeLEB`Wk|t*=)twO(|UNR(zh-YDK^DpZ~J7OH2H7Kx}wICvbaY2a84<~fgDGDN$KvN z(&lr$2Z3+68~i=gu=H6l<5ReT@tpxW5%kl*!(vLZDu#EA*cDa)4q)j)EPWf(Su!$| z1%qGW0FV!Ew5+$`QsbohW76>B6_#knm~C-kTl&2pn#bGk{6|sgYiv~j3kv~WocMXy zG2LraV1NJC;z(3If$$r&e9AXHIjeS8k*`$rGP1zzJe4MI)^~A;ik)iX80>GQDocrqjiZn8D^YEsSFgdfbs8W1Hy8u;Jon@X?UOfvt z24Bn18%E<*aj0N zA{eqGV0fM))lw3-m+AaBbGZTR7UAqYl)kZFYQ{H1#8=(LxH6u6wWWB6_o!)QHe`DAln#C--=nku)A4MPLKhbgN*pVPrRR9eE z0=MUuU39TxEr7A%TS&{QwP0=X7eWq|yCWs38&Y`C8V6CwjRy0ZgK7a;Z@b@B2o^ov zwM5z?K=%)_f)qOdZP`&_V_%!`Uf1{15C7NU)5<)>kP@yePTg($YsimO+${3#Xl>?c zxdxi{i}vDSvfR7sVzE%k!Dt54V+V zI@lt^(fU?TNkq=Zg{R6Q`^ep}9c{Gm_f+-Op=9a(Yt$Keuo<9Z7T@MJ(xY!^@2vcc zi+mYa!^#2~n6JZeVm-BZl%^b!83_vMyZ-=C_)oHo17>7J759*=1;Jh)MHmrJH<(MK zh97#@A5|doC>rh;9KoPAN$0)SYXEydKSeAv&}6XyvXS06QzXYun^CTvl~Ah7)7a>5 z$FjEOGqpX3cXd(3o)xz-ovT(eZHy=;!rB_u1|v5B_Y+8K!~$(V)`uGA^wX>u02C@x z?zV2-Xgvki{{TUzP0}-C-D*~mw8jj+;6A#}(qr<2b^G|#=|BI_;LFapsP{D!lK7jE6_8V(&_R_qK8f>Xtfhy`W40RsnAFhlzT5YkA`jB|@1K&jQ z8kqx)tEl8TTeymN3`ECp+zpB4uD$gZ959YH9poLScI`MCn-#5ESj!dMx3SOzb>=

    ELG3chtTsg)#EXsp{B?I!>QwEYEX_0N*lxM>tePs(E27!+!ot!2N(B%Pn`{B-5~iyp*_7BbBQjTST){NGTv z*&F`=mjFM~-Tr@>zU5xCu_@EKt44^=vJ6KT!@*Igj`s5(_nY9qj#lw}H63yyS0S+{ zw&+v^;^g>=9rd0nP%@;zOnDJNQzfpV=HzhWPnde&rS+DN!#FXqxDR`;4GmRFec0zqk-%kgRew|zVY8`KcQTgd+a$ZZdnNw>JtwnLJdx*NAZ8}P!D1> z#DWNaWz_u!i3Y6wF)S6YW7unMc}G=b#BpKI?X6^GY03KPbl{{Xn=!W7`409!DDSN& zgnW;K>ujgW5VOYY!+C-|NMGLJPkfz=e^2XpSvF8iUJH*i!>?sqI+HJss2{hN8swoTR z5Ada-|IzvUvTnYiTTV8y?F84Gk4d|;r5!ot%-q{v5ZB_vf@Rn z*j7sn77mfbv{8BfGs?!CD{N1s^xgiAE zO^GJfF63y;2L2jsQ7BUROTi+R%^@zQZ?CB8-c{Ozh!6CG@3?){{S^^ID=4ZWMSNm zny+VlD^(tg1fLRd0F8U;eUB1&k-TIFQbO?uUO%)_`%#d_{{ScYV6u^QCygId)?@23 z#^JWvufvh|8hDW(sm(iX3ZY4~k+8V#J*)gQS}`MuJi%b=ec~xO*QwA9Uzb<^ORXI(oP6HbNPXSwV8PvQvQNHGUSBtO6|dXF zKd+VN(|_E29JOe(UGBCbc0z=1*372gRX%!^9dTmeht#@$YVO6zNxPJbk9AW#1E{iq zM?2NXNRZ=yzLhr3k^nOI(h?LHlZek;y8l1P4aA0d&`p1Bs<(n-XqV!l~X^^IL1# zxOa1>3Xdi>SLI{!)60ppj>p8zPNvL$6HD?50JnEcM)G5-K9R;La0xWGWdQU&&*AaLTxPUWfV=Xju#og@x0IH4Bz@8=BCdMYiR<($`3*L!9xaY~7>R7W}0(1&AH>YZGYQ!=uiu)(*Du0C(uRT7iLl~t}s5o7uqsY;}x$SMZiEyAl) zfc1W%l)XL1IBmyCHt4}mI~uRCVxrkuLMM(t2F+aDDmLyb_+0b(YA?e`f2MUVnz$_| zP-7HPw%;xjZSD7({{RiCb7Y=H)bCE~xY1*XanQ#SsCDy$769r@hnd%vRQ6}VeG)PC z#Yt7-k{ynte4B!zpyy*|X1@ie_b;kfr1dTGlb1SAk0IRzikDk(YlGq%d{n;w0G&I+ zV0CFaK2&+wkvxZR5_xO_fPNFh;iuPmzpZ)g(dcr_SIAtmgRK)HKn1n2{8LAC>!$gUw^vZ`jG$A`Lbtn>>E?a*Do>O zM}|nGj5{D#Tad@^4m$VLS!Eh;24dF%LXAOQRy;uLsO~CPoo_C}ZP;4tuWiQ~M$2vT z!;%4AJky1ga-tljW^u7#5Rb+7+_@gBm zf!%9rZ^+()5+pJsW$PG;d@{%wc>WdIx4rIe7OH%eCmi6J0njCtNEa)Ah_UA*_k2GM zS5x4*zDYP18%A-k?Gci5}B->H0 zyv;kAQRxQ7h~P~ z#+3_1Yh)Y<<9bw#EI`{$O{`5L88Nq99W~eWngE9w9c`yR`%nx=6pLb4!+)yO>?me|+APA$03^;d+G_kmx0i}mwqed`t8(lrBX=uhIWu69#P#s9n z9w7Hxu2QO(P3gJaM=pFwWd2kei_It^J@@db=dX1>hbt#b=&|BJqibZ#7$!#80A?Hx zylwLS8qZXh$IqEs(vrkou7hx}9}5w{e6)Sf>S8468E_bm0)AbPi4ryOqj3ctb<>qU zw566D=%18^2giyzPmo9hAO=%@BI3t|IiCe}Efz0Vks0tMNn-?gaF*EWr;3tvHt+bB zoR)kR&&uiO{4$8`LfCe;B-D6d$@7uT(0IK0JCmy2^Q?s z@dk<_Ljgd(^ko2PBHvNpZ7V*G4>gr9q>=8qaMmDq4pk~Iku*#^9vl3p?cu_l2fWnu=Z1+Ol{s&jk|ndtcpMv?H#|nORtd);gkW>U|SoYI{5hoUKhF4wJHwH zy|C0r6EKDLqd8!vKnU=u)z0EgGSp;|#}sBZTkR!_HohUp!Zg$F)6*d=GAgvim=ojJ z2gTlRSIwUZu_@6Xb1Zgd5-TP4js#lf#@Z3vQ{3_;@X;79=$%Lfwz0T2*Ow1{k}uu`xKg2Q=yUnN zMo)}d;E-?r6ctUjC}0X|dwJA{yqO{bz*v3nwv7#g_-sz3*iZ%;0d5Zw#CB_Hpdw2d zUkTJ(Q{POQ%OYvor$a`Ey!Rk@uig78>0pt)+h`=Q;oEvN8j}Ygs_$i8#@pD@qXcXT zG2$&NEQ{EuUu{3O2^ohOVlaerh+Lm3ZMjYLV%FzVdi<(Hk)I|5vQ3gSekHNmb|1Tc z4z&K%v9u*~3*^KK%@DX33%mjG6&h~;0CiT3HPIWV$nr{5#9Rg?2>^RR(^{Xj(Pcjd zNb_XH$i`;?0T4~0*-oXw2ZgxPozZzY&?Fd;x=Xe^eXNpeZB6Zd{{VeY7Vq?ZNSIuw zSumauGEz1PGB=CDHCHF*UzNWLR+jiiI_@||f;S1Xa)YE=-%JO_dLIiEyf4y1t{h6%h znPLTjTk})8-%k-y6%o%Xxh6&`tI0*1+1*uvm|{>vviL#V9FT3;JIxk@pZOBV0w`8s z0w0);XK2&->Su^)d`t#5^Fa=vd2xkPe`p>Rr3I~etH!wKv9;uFHx{+P7Pz+r9i!r= zNSA`e8Ycdbu-jml?Z0KuI(w#4NOG2Na(0;5knB#k=WA=-S2OYQky|EAUZE6`?N!sv zLb?lEjr+gL^3$GTZ;F0*mE&2}N^T@=OEBGSbvN*zinFuJO)S$Fjhgo*NNt319lCR( zLbP~gi9)i4-~oGILB_)W0P3QHaXd_XE3m>mjwDk}iEk}f0mL5L8dx5i1|S2W4#bOEo&r+z{I7~MFMc*0X#ZXpSWsKypwhCQQ?c&e0)!352Q`Ef~jB0Hv8IIk}aO!95A@!`rw z81fgwVxdnT=Gx=~*tJ?%XQRi3g@PoWa=v1;Y+s0R@Ysr(PYo>CDy=3wm&bjgWdiI) zu1fcEq6Q_;P|-J<0G2x;;Oa>`KqJL{qL@ubg%E-hk>X~&?)JKs8f+thE&iHv(oOVC zVU7IK#WAP;oFw=`Cq)hq*r8dQ7MP04^SWf*7>2W;XE#E z#GBY(vbp1~yrN53i#^D{vWte4z}!`VyI$Ab;{9Jxth)J8`IQh_M;q;+UkZ!%sJ^u| zQ6DOj#!HZZXaE2ZIqO8AXtAYu!&_i&Y@^MgqRjq>u-O z=iy(&M5-a3fM$X~ArY)A;cM?;M`^GXeB7zNNn;#|FcoBja;Q6<&ZUyzp59`#>&Hmv z;KxbndHHxtjT}zoJ(TJ+;;qWtXzKYB;bO(~FH*>Xzcj)ug*OCtj(oMO_9z4BKD!s2 zBC|mfF!IZ@|wLc&sP zqWlS?L5`7;14v$N7-U&GSS~W@ z;TU9P-wtT{6&bn!O7gCFFnG+)Y>t4h4(%295%nYf|l8L?byD&E$^`BoV ziMI6bP$5W&vnaOi3Qz8eil|CoPxW8S10)-n&G%R}zQv~?)T;V#F*XgmgKBpYN&P#i z9H%7o#G)WfDo-WkKCMkG8kFgsA0ilR`ZH;#hfe+^(WOs6NmCVq`0Zk87%R}RP%oY? zoYa6p_fj!G;<{9?<&1DtSlsLO(e^AAx<^Hfl3_p^+xTm0KGb2eJ{B};abZwC@Dy4w ztcF~PEgRj~-GxcLkJOhNC8EgFX8}*hKMz%e!6d}o{Gd^Qt4TIgci+c{XrhAgV+EUJ z(w8HE<7$x1GWABx3lUPQ+s3FG%9}Dd0g@ok_)P)fHV0FL!~r&4rkHwH6}>28?C znOn?=8-~BtMvOe&KMxK)`#_KG+fz&Az!`FwUN0%0CN>3mQ-x6Q^og^&v#8wOSp>%1 z0jFriucu<+ra)}hQ!FY#w*hWKmYA{(7s_eoJ04;dC|ik3akVlZ{xcF@my%sC6U4z# zec`AgoS_5Jv7Ri1k(kAiDDul&!h6jio55+40UXYfq)9?8Vd;b9Gui0Ik{5bE)ie&Wx>I47Tb%!nT literal 0 HcmV?d00001 From e459ab2e34ddbb463ffee926a12aba11b97b1b9d Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Tue, 14 Dec 2021 16:03:53 -0800 Subject: [PATCH 0563/1254] Refactored to use dspace-api utility class shared with CLI tool. --- .../dspace/app/iiif/IIIFServiceFacade.java | 4 +- .../app/iiif/service/ManifestService.java | 6 ++- .../app/iiif/service/utils/IIIFUtils.java | 53 +------------------ 3 files changed, 9 insertions(+), 54 deletions(-) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java index 7bb723ea65..9395c8db89 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java @@ -7,6 +7,8 @@ */ package org.dspace.app.iiif; +import static org.dspace.iiif.Utils.isIIIFEnabled; + import java.sql.SQLException; import java.util.UUID; @@ -77,7 +79,7 @@ public class IIIFServiceFacade { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - if (item == null || !utils.isIIIFEnabled(item)) { + if (item == null || !isIIIFEnabled(item)) { throw new ResourceNotFoundException("IIIF manifest for id " + id + " not found"); } return manifestService.getManifest(item, context); 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 794fbb46ca..a3c90fbf8c 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 @@ -7,6 +7,8 @@ */ package org.dspace.app.iiif.service; +import static org.dspace.iiif.Utils.getIIIFBundles; + import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -141,7 +143,7 @@ public class ManifestService extends AbstractResourceService { * @param manifestId the generated manifestId */ private void addRanges(Context context, Item item, String manifestId) { - List bundles = utils.getIIIFBundles(item); + List bundles = getIIIFBundles(item); RangeGenerator root = new RangeGenerator(rangeService); root.setLabel(I18nUtil.getMessage("iiif.toc.root-label")); root.setIdentifier(manifestId + "/range/r0"); @@ -331,7 +333,7 @@ public class ManifestService extends AbstractResourceService { * @param context DSpace context */ private void addRendering(Item item, Context context) { - List bundles = utils.getIIIFBundles(item); + List bundles = getIIIFBundles(item); for (Bundle bundle : bundles) { List bitstreams = bundle.getBitstreams(); for (Bitstream bitstream : bitstreams) { 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 79c844948f..612f4d38fc 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 @@ -7,6 +7,8 @@ */ package org.dspace.app.iiif.service.utils; +import static org.dspace.iiif.Utils.getIIIFBundles; + import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -27,9 +29,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.BitstreamService; -import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.license.CreativeCommonsServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -76,55 +76,6 @@ public class IIIFUtils { @Autowired protected BitstreamService bitstreamService; - /** - * This method returns the bundles holding IIIF resources if any. - * If there is no IIIF content available an empty bundle list is returned. - * @param item the DSpace item - * - * @return list of DSpace bundles with IIIF content - */ - public List getIIIFBundles(Item item) { - boolean iiif = isIIIFEnabled(item); - List bundles = new ArrayList<>(); - if (iiif) { - bundles = item.getBundles().stream().filter(b -> isIIIFBundle(b)).collect(Collectors.toList()); - } - return bundles; - } - - /** - * This method verify if the IIIF feature is enabled on the item or parent collection. - * - * @param item the dspace item - * @return true if the item supports IIIF - */ - public boolean isIIIFEnabled(Item item) { - return item.getOwningCollection().getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")) - || item.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")); - } - - /** - * Utility method to check is a bundle can contain bitstreams to use as IIIF - * resources - * - * @param b the DSpace bundle to check - * @return true if the bundle can contain bitstreams to use as IIIF resources - */ - private boolean isIIIFBundle(Bundle b) { - return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, - Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", - "BRANDED_PREVIEW", "TEXT", OTHER_CONTENT_BUNDLE) - && b.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); - } - /** * Return all the bitstreams in the item to be used as IIIF resources * From 0407751fc2c56a1bba1ba640350aa88d642d8322 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Tue, 14 Dec 2021 16:39:08 -0800 Subject: [PATCH 0564/1254] Moved commit to CLI class. --- .../org/dspace/app/canvasdimension/CanvasDimensionCLI.java | 2 ++ .../app/canvasdimension/IIIFCanvasDimensionServiceImpl.java | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java index f3cebac910..ff7a96f0fa 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java @@ -180,6 +180,8 @@ public class CanvasDimensionCLI { default: break; } + // commit changes + context.commit(); } } diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java index fee8345db6..d70b2fb37f 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java @@ -139,8 +139,6 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic if (isIIIFItem) { if (processItemBundles(context, item)) { ++processed; - // commit changes - context.commit(); } } } @@ -164,7 +162,7 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic } if (done) { // update the item - itemService.update(context, item); + // itemService.update(context, item); if (!isQuiet) { System.out.println("Updated canvas metadata for item: " + item.getID()); } From aa0840668aca847c57410c2c9be1cd551c0ae841 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Tue, 14 Dec 2021 16:39:47 -0800 Subject: [PATCH 0565/1254] IT update. --- .../dspace/app/canvasdimensions/CanvasDimensionsIT.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java b/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java index d321761ecf..14c1bbe57d 100644 --- a/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java +++ b/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java @@ -43,19 +43,18 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { @Before public void setup() throws IOException { context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") + .withName("Sub Community 1") .build(); - col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - - // second sub-community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) .withName("Sub Community 2") .build(); + col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); col2 = CollectionBuilder.createCollection(context, child2).withName("Collection 2").build(); context.restoreAuthSystemState(); From 881dd059cc6060f73d55a72d7a92341a3b40e42d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 15 Dec 2021 11:41:31 +0100 Subject: [PATCH 0566/1254] changed category of endpoint from core to tools --- .../java/org/dspace/app/rest/model/FeedbackRest.java | 2 +- .../org/dspace/app/rest/FeedbackRestRepositoryIT.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java index 5ab04aa062..00f1e92c87 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/FeedbackRest.java @@ -19,7 +19,7 @@ public class FeedbackRest extends BaseObjectRest { private static final long serialVersionUID = 1L; public static final String NAME = "feedback"; - public static final String CATEGORY = RestAddressableModel.CORE; + public static final String CATEGORY = RestAddressableModel.TOOLS; private String page; private String email; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java index 1e67000974..fd61f7a7a2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/FeedbackRestRepositoryIT.java @@ -41,14 +41,14 @@ public class FeedbackRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void findAllTest() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/core/feedbacks")) + getClient(authToken).perform(get("/api/tools/feedbacks")) .andExpect(status().isMethodNotAllowed()); } @Test public void findOneTest() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(get("/api/core/feedbacks/1")) + getClient(authToken).perform(get("/api/tools/feedbacks/1")) .andExpect(status().isMethodNotAllowed()); } @@ -67,7 +67,7 @@ public class FeedbackRestRepositoryIT extends AbstractControllerIntegrationTest feedbackRest.setMessage("My feedback!"); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/feedbacks") + getClient(authToken).perform(post("/api/tools/feedbacks") .content(mapper.writeValueAsBytes(feedbackRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -96,7 +96,7 @@ public class FeedbackRestRepositoryIT extends AbstractControllerIntegrationTest feedbackRest.setMessage("My feedback!"); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/feedbacks") + getClient(authToken).perform(post("/api/tools/feedbacks") .content(mapper.writeValueAsBytes(feedbackRest)) .contentType(contentType)) .andExpect(status().isNotFound()); @@ -119,7 +119,7 @@ public class FeedbackRestRepositoryIT extends AbstractControllerIntegrationTest feedbackRest.setMessage("My feedback!"); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/feedbacks") + getClient(authToken).perform(post("/api/tools/feedbacks") .content(mapper.writeValueAsBytes(feedbackRest)) .contentType(contentType)) .andExpect(status().isBadRequest()); From c5743c9bae048894758baa2785fd2fd78293147e Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 15 Dec 2021 14:53:01 +0100 Subject: [PATCH 0567/1254] 85897: Issue#8042: IT demonstrating that if workflow user rejects an item they submitted, they can't edit the item that was rejected anymore --- .../xmlworkflow/XmlWorkflowServiceIT.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java new file mode 100644 index 0000000000..fce1e9ebac --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java @@ -0,0 +1,109 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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; + +import static org.junit.Assert.assertTrue; + +import java.sql.SQLException; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.ClaimedTaskBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.discovery.IndexingService; +import org.dspace.eperson.EPerson; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; +import org.dspace.xmlworkflow.service.XmlWorkflowService; +import org.dspace.xmlworkflow.state.Workflow; +import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * IT for {@link XmlWorkflowServiceImpl} + * + * @author Maria Verdonck (Atmire) on 14/12/21 + */ +public class XmlWorkflowServiceIT extends AbstractIntegrationTestWithDatabase { + + protected XmlWorkflowService xmlWorkflowService = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService(); + protected IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(IndexingService.class.getName(), + IndexingService.class); + protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + + /** + * Test to verify that if a user submits an item into the workflow, then it gets rejected that the submitter gets + * write access back on the item + * + * @throws Exception + */ + @Test + public void workflowUserRejectsItemTheySubmitted_ItemShouldBeEditable() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson submitter = EPersonBuilder.createEPerson(context).withEmail("submitter@example.org").build(); + context.setCurrentUser(submitter); + Community community = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection colWithWorkflow = CollectionBuilder.createCollection(context, community) + .withName("Collection WITH workflow") + .withWorkflowGroup(1, submitter) + .build(); + Workflow workflow = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory().getWorkflow(colWithWorkflow); + ClaimedTask taskToReject = ClaimedTaskBuilder.createClaimedTask(context, colWithWorkflow, submitter) + .withTitle("Test workflow item to reject").build(); + + // Submitter person is both original submitter as well as reviewer, should have edit access of claimed task + assertTrue(this.containsRPForUser(taskToReject.getWorkflowItem().getItem(), submitter, Constants.WRITE)); + + // reject + MockHttpServletRequest httpRejectRequest = new MockHttpServletRequest(); + httpRejectRequest.setParameter("submit_reject", "submit_reject"); + httpRejectRequest.setParameter("reason", "test"); + executeWorkflowAction(httpRejectRequest, workflow, taskToReject); + + // Submitter person is both original submitter as well as reviewer, should have edit access of reject, i.e. + // sent back/to submission task + assertTrue(this.containsRPForUser(taskToReject.getWorkflowItem().getItem(), submitter, Constants.WRITE)); + } + + private boolean containsRPForUser(Item item, EPerson user, int action) throws SQLException { + List rps = authorizeService.getPolicies(context, item); + for (ResourcePolicy rp : rps) { + if (rp.getEPerson().getID().equals(user.getID()) && rp.getAction() == action) { + return true; + } + } + return false; + } + + private void executeWorkflowAction(HttpServletRequest httpServletRequest, Workflow workflow, ClaimedTask task) + throws Exception { + final EPerson previousUser = context.getCurrentUser(); + task = context.reloadEntity(task); + context.setCurrentUser(task.getOwner()); + xmlWorkflowService + .doState(context, task.getOwner(), httpServletRequest, task.getWorkflowItem().getID(), workflow, + workflow.getStep(task.getStepID()).getActionConfig(task.getActionID())); + context.commit(); + indexer.commit(); + context.setCurrentUser(previousUser); + } +} From 012b21582746ecae9ef73a0e10ed3fb316406cf6 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 15 Dec 2021 14:53:55 +0100 Subject: [PATCH 0568/1254] 85897: Issue#8042: Fix for if workflow user rejects an item they submitted, they can bow edit the item that was rejected again --- .../org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 5525e42fcc..54122d6ea7 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -1031,12 +1031,13 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { itemService.update(context, myitem); - // convert into personal workspace - WorkspaceItem wsi = returnToWorkspace(context, wi); - // remove policy for controller removeUserItemPolicies(context, myitem, e); revokeReviewerPolicies(context, myitem); + + // convert into personal workspace + WorkspaceItem wsi = returnToWorkspace(context, wi); + // notify that it's been rejected notifyOfReject(context, wi, e, rejection_message); log.info(LogHelper.getHeader(context, "reject_workflow", "workflow_item_id=" From a617ef8bc0f70c766410713b1159b69022f45dad Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 15 Dec 2021 16:15:56 +0100 Subject: [PATCH 0569/1254] refactoring --- .../submit/model/AccessConditionOption.java | 32 ++++++++++++++++-- .../AccessConditionAddPatchOperation.java | 11 ++++--- ...tionDiscoverableReplacePatchOperation.java | 7 ++-- .../AccessConditionRemovePatchOperation.java | 2 ++ .../AccessConditionReplacePatchOperation.java | 33 +++++++++++-------- .../AccessConditionResourcePolicyUtils.java | 17 ++++------ 6 files changed, 66 insertions(+), 36 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java index 1c981a8b09..37385febea 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionOption.java @@ -196,7 +196,17 @@ public class AccessConditionOption { endDate); } - public boolean canCreateResourcePolicy(Context context, String name, Date startDate, Date endDate) + /** + * Validate the policy properties, throws exceptions if any is not valid + * + * @param context DSpace context + * @param name Name of the resource policy + * @param startDate Start date of the resource policy. If {@link #getHasStartDate()} + * returns false, startDate should be null. Otherwise startDate may not be null. + * @param endDate End date of the resource policy. If {@link #getHasEndDate()} + * returns false, endDate should be null. Otherwise endDate may not be null. + */ + public void canCreateResourcePolicy(Context context, String name, Date startDate, Date endDate) throws SQLException, AuthorizeException, ParseException { if (getHasStartDate() && Objects.isNull(startDate)) { throw new IllegalStateException("The access condition " + getName() + " requires a start date."); @@ -236,13 +246,29 @@ public class AccessConditionOption { getName(), getEndDateLimit() )); } - return true; } + /** + * Create a new resource policy for a DSpaceObject + * NOTICE: This method does not take care of validating the parameters + * like name, startDate and endDate, before executing it you should invoke + * the #canCreateResourcePolicy method so validating the parameters. + * + * @param context DSpace context + * @param obj DSpaceObject for which resource policy is created + * @param name Name of the resource policy + * @param description Description of the resource policy + * @param startDate Start date of the resource policy. If {@link #getHasStartDate()} + * returns false, startDate should be null. Otherwise startDate may not be null. + * @param endDate End date of the resource policy. If {@link #getHasEndDate()} + * returns false, endDate should be null. Otherwise endDate may not be null. + * @return ResourcePolicy + */ public ResourcePolicy createPolicy(Context context, DSpaceObject obj, String name, String description, Date startDate, Date endDate) throws SQLException, AuthorizeException { Group group = groupService.findByName(context, getGroupName()); return authorizeService.createResourcePolicy(context, obj, group, null, READ, TYPE_CUSTOM, name, description, startDate, endDate); } -} + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java index 1841a3c87a..228f91fda8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java @@ -48,21 +48,22 @@ public class AccessConditionAddPatchOperation extends AddPatchOperation/accessConditions/-" - String[] split = getAbsolutePath(path).split("/"); - Item item = source.getItem(); + Item item = source.getItem(); List accessConditions = Arrays.asList(evaluateSingleObject((LateObjectEvaluator) value)); verifyAccessConditions(context, configuration, accessConditions); - // check duplicate policy - checkDuplication(context, item, accessConditions); + //"path": "/sections/<:name-of-the-form>/accessConditions/-" + String[] split = getAbsolutePath(path).split("/"); if (split.length == 1) { // to replace completely the access conditions authorizeService.removePoliciesActionFilter(context, item, Constants.READ); } + // check duplicate policy + checkDuplication(context, item, accessConditions); + // apply policies AccessConditionResourcePolicyUtils.findApplyResourcePolicy(context, configuration.getOptions(), item, accessConditions); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java index 3577ec977c..880c59bb2d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java @@ -32,7 +32,6 @@ public class AccessConditionDiscoverableReplacePatchOperation extends ReplacePat @Override void replace(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String string, Object value) throws Exception { - Boolean discoverable = null; String stepId = (String) currentRequest.getAttribute("accessConditionSectionId"); AccessConditionConfiguration configuration = accessConditionConfigurationService.getMap().get(stepId); @@ -42,6 +41,7 @@ public class AccessConditionDiscoverableReplacePatchOperation extends ReplacePat " the user to specify the visibility of the item"); } + Boolean discoverable; if (value instanceof String) { discoverable = BooleanUtils.toBooleanObject((String) value); } else { @@ -57,11 +57,8 @@ public class AccessConditionDiscoverableReplacePatchOperation extends ReplacePat if (discoverable == item.isDiscoverable()) { return; - } else if (discoverable) { - item.setDiscoverable(discoverable); - } else { - item.setDiscoverable(discoverable); } + item.setDiscoverable(discoverable); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java index 114c082000..bf731d4b98 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionRemovePatchOperation.java @@ -64,6 +64,8 @@ public class AccessConditionRemovePatchOperation extends RemovePatchOperation/accessConditions/0" String[] split = getAbsolutePath(path).split("/"); Item item = source.getItem(); - int toReplace = Integer.parseInt(split[1]); + Integer idxToReplace = null; + try { + idxToReplace = Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + throw new UnprocessableEntityException("The provided index format is not correct! Must be a number!"); + } List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); - if (toReplace < 0 || toReplace >= policies.size()) { - throw new UnprocessableEntityException("The provided index:" + toReplace + " is not supported," + if (idxToReplace < 0 || idxToReplace >= policies.size()) { + throw new UnprocessableEntityException("The provided index:" + idxToReplace + " is not supported," + " currently the are " + policies.size() + " access conditions"); } @@ -68,9 +73,10 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< AccessConditionDTO accessConditionDTO = evaluateSingleObject((LateObjectEvaluator) value); if (Objects.nonNull(accessConditionDTO) && Objects.nonNull(getOption(configuration, accessConditionDTO))) { verifyAccessCondition(context, configuration, accessConditionDTO); - if (checkDuplication(context, policies, accessConditionDTO, toReplace, item)) { + if (checkDuplication(context, policies, accessConditionDTO, idxToReplace, item)) { + item.getResourcePolicies().remove(policies.get(idxToReplace)); context.commit(); - resourcePolicyService.delete(context, policies.get(toReplace)); + resourcePolicyService.delete(context, policies.get(idxToReplace)); AccessConditionOption option = getOption(configuration, accessConditionDTO); option.createPolicy(context, item, accessConditionDTO.getName(), null, accessConditionDTO.getStartDate(),accessConditionDTO.getEndDate()); @@ -79,10 +85,12 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< } else if (split.length == 3) { String valueToReplace = getValue(value); String attributeReplace = split[2]; - ResourcePolicy rpToReplace = policies.get(toReplace); + ResourcePolicy rpToReplace = policies.get(idxToReplace); AccessConditionDTO accessConditionDTO = createDTO(rpToReplace, attributeReplace, valueToReplace); verifyAccessCondition(context, configuration, accessConditionDTO); updatePolicy(context, valueToReplace, attributeReplace, rpToReplace); + } else { + throw new UnprocessableEntityException("The patch operation for path:" + path + " is not supported!"); } } @@ -108,7 +116,6 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< ResourcePolicy policyToReplace = policies.get(toReplace); // check if the resource policy is of the same type if (policyToReplace.getRpName().equals(accessConditionDTO.getName())) { - item.getResourcePolicies().remove(policyToReplace); return true; } // check if there is not already a policy of the same type @@ -117,7 +124,6 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< return false; } } - item.getResourcePolicies().remove(policyToReplace); return true; } @@ -132,10 +138,10 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< accessCondition.setName(valueToReplare); return accessCondition; case "startDate": - accessCondition.setStartDate(parsDate(valueToReplare)); + accessCondition.setStartDate(parseDate(valueToReplare)); return accessCondition; case "endDate": - accessCondition.setEndDate(parsDate(valueToReplare)); + accessCondition.setEndDate(parseDate(valueToReplare)); return accessCondition; default: throw new UnprocessableEntityException("The provided attribute: " @@ -150,17 +156,18 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< rpToReplace.setRpName(valueToReplare); break; case "startDate": - rpToReplace.setStartDate(parsDate(valueToReplare)); + rpToReplace.setStartDate(parseDate(valueToReplare)); break; case "endDate": - rpToReplace.setEndDate(parsDate(valueToReplare)); + rpToReplace.setEndDate(parseDate(valueToReplare)); break; default: + throw new IllegalArgumentException("Attribute to replace is not valid:" + attributeReplace); } resourcePolicyService.update(context, rpToReplace); } - private Date parsDate(String date) { + private Date parseDate(String date) { List knownPatterns = Arrays.asList( new SimpleDateFormat("yyyy-MM-dd"), new SimpleDateFormat("dd-MM-yyyy"), diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java index efc2ecf05f..dc601c4b80 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionResourcePolicyUtils.java @@ -41,7 +41,6 @@ public class AccessConditionResourcePolicyUtils { } } - public static void findApplyResourcePolicy(Context context, List accessConditionOptions, DSpaceObject obj, String name, String description, Date startDate, Date endDate) throws SQLException, AuthorizeException, ParseException { @@ -57,14 +56,12 @@ public class AccessConditionResourcePolicyUtils { } } - public static boolean canApplyResourcePolicy(Context context, - List accessConditionOptions, String name, Date startDate, Date endDate) - throws SQLException, AuthorizeException, ParseException { - for (AccessConditionOption accessConditionOption : accessConditionOptions) { - if (accessConditionOption.getName().equalsIgnoreCase(name)) { - return accessConditionOption.canCreateResourcePolicy(context, name, startDate, endDate); - } - } - throw new UnprocessableEntityException("The provided policy: " + name + " is not supported!"); + public static void canApplyResourcePolicy(Context context, List accessConditionOptions, + String name, Date startDate, Date endDate) throws SQLException, AuthorizeException, ParseException { + accessConditionOptions.stream() + .filter(ac -> ac.getName().equalsIgnoreCase(name)) + .findFirst() + .orElseThrow(() -> new UnprocessableEntityException("The provided policy: " + name + " is not supported!")); } + } \ No newline at end of file From 455429ce6abdae5c5b9349871e674d050baad526 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 15 Dec 2021 18:55:54 -0800 Subject: [PATCH 0570/1254] Multiple updates to configs, processing classes and shared utils. --- .../canvasdimension/CanvasDimensionCLI.java | 32 ++++-- .../IIIFApiQueryServiceImpl.java | 15 +-- .../IIIFCanvasDimensionServiceImpl.java | 100 +++++------------- .../canvasdimension/ImageDimensionReader.java | 5 + .../org/dspace/app/canvasdimension/Util.java | 5 + .../IIIFCanvasDimensionServiceFactory.java | 5 + ...IIIFCanvasDimensionServiceFactoryImpl.java | 7 +- .../service/IIIFApiQueryService.java | 8 ++ .../service/IIIFCanvasDimensionService.java | 40 ++++++- .../iiif/{Utils.java => IIIFSharedUtils.java} | 13 ++- .../CanvasDimensionsIT.java | 71 ++++++++----- .../cat.jpg | Bin .../dspace/app/iiif/IIIFServiceFacade.java | 4 +- .../app/iiif/service/ManifestService.java | 6 +- .../app/iiif/service/utils/IIIFUtils.java | 54 +--------- .../spring/api/core-factory-services.xml | 1 - dspace/config/spring/api/core-services.xml | 4 - dspace/config/spring/api/iiif-processing.xml | 10 ++ 18 files changed, 201 insertions(+), 179 deletions(-) rename dspace-api/src/main/java/org/dspace/iiif/{Utils.java => IIIFSharedUtils.java} (90%) rename dspace-api/src/test/java/org/dspace/app/{canvasdimensions => canvasdimension}/CanvasDimensionsIT.java (91%) rename dspace-api/src/test/resources/org/dspace/app/{canvasdimensions => canvasdimension}/cat.jpg (100%) create mode 100644 dspace/config/spring/api/iiif-processing.xml diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java index ff7a96f0fa..6ba204f579 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java @@ -32,6 +32,11 @@ import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +/** + * Sets IIIF canvas metadata on bitstreams based on image size. + * + * @author Michael Spalti mspalti@willamette.edu + */ public class CanvasDimensionCLI { private static final EPersonService epersonService = EPersonServiceFactory.getInstance().getEPersonService(); @@ -42,11 +47,6 @@ public class CanvasDimensionCLI { public static void main(String[] argv) throws Exception { - boolean force = false; - boolean isQuiet = false; - String identifier = null; - String eperson = null; - int max2Process = Integer.MAX_VALUE; boolean iiifEnabled = configurationService.getBooleanProperty("iiif.enabled"); if (!iiifEnabled) { @@ -54,6 +54,16 @@ public class CanvasDimensionCLI { System.exit(0); } + // default to not updating existing dimensions + boolean force = false; + // default to printing messages + boolean isQuiet = false; + // default to no limit + int max2Process = Integer.MAX_VALUE; + + String identifier = null; + String eperson = null; + Context context = new Context(); IIIFCanvasDimensionService canvasProcessor = IIIFCanvasDimensionServiceFactory.getInstance() .getIiifCanvasDimensionService(); @@ -145,7 +155,7 @@ public class CanvasDimensionCLI { EPerson user; if (eperson == null) { - System.out.println("You must provide an eperson."); + System.out.println("You must provide an eperson using the \"-e\" flag."); System.exit(1); } @@ -167,21 +177,27 @@ public class CanvasDimensionCLI { canvasProcessor.setMax2Process(max2Process); canvasProcessor.setIsQuiet(isQuiet); + int processed = 0; switch (dso.getType()) { case Constants.COMMUNITY: - canvasProcessor.processCommunity(context, (Community) dso); + processed = canvasProcessor.processCommunity(context, (Community) dso); break; case Constants.COLLECTION: - canvasProcessor.processCollection(context, (Collection) dso); + processed = canvasProcessor.processCollection(context, (Collection) dso); break; case Constants.ITEM: canvasProcessor.processItem(context, (Item) dso); + processed = 1; break; default: break; } // commit changes context.commit(); + + // Always print summary to standard out. + System.out.println(processed + " IIIF items were processed."); + } } diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java index 827d67b4e9..609fa54041 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java @@ -23,9 +23,16 @@ import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; + +/** + * Queries the configured IIIF server for image dimensions. Used for + * formats that cannot be easily read using ImageIO (jpeg 2000). + * + * @author Michael Spalti mspalti@willamette.edu + */ public class IIIFApiQueryServiceImpl implements IIIFApiQueryService, InitializingBean { - @Autowired(required = true) + @Autowired() protected ConfigurationService configurationService; String iiifImageServer; @@ -35,11 +42,7 @@ public class IIIFApiQueryServiceImpl implements IIIFApiQueryService, Initializin iiifImageServer = configurationService.getProperty("iiif.image.server"); } - /** - * Returns array with canvas height and width - * @param bitstream - * @return - */ + @Override public int[] getImageDimensions(Bitstream bitstream) { return getIiifImageDimensions(bitstream); } diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java index fe9ca20563..e03e32243d 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java @@ -8,7 +8,6 @@ package org.dspace.app.canvasdimension; import java.io.InputStream; -import java.sql.SQLException; import java.util.Iterator; import java.util.List; import java.util.Optional; @@ -26,78 +25,59 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.iiif.Utils; +import org.dspace.iiif.IIIFSharedUtils; import org.springframework.beans.factory.annotation.Autowired; +/** + * This service sets canvas dimensions for bitstreams. Processes communities, + * collections, and individual items. + * + * @author Michael Spalti mspalti@willamette.edu + */ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionService { - @Autowired(required = true) + @Autowired() ItemService itemService; - @Autowired(required = true) + @Autowired() CommunityService communityService; - @Autowired(required = true) + @Autowired() BitstreamService bitstreamService; - @Autowired(required = true) + @Autowired() DSpaceObjectService dSpaceObjectService; - @Autowired(required = true) + @Autowired() IIIFApiQueryService iiifApiQuery; - // field used to check for existing bitstream metadata - private static final String IIIF_WIDTH_METADATA = "iiif.image.width"; - private boolean forceProcessing = false; private boolean isQuiet = false; private List skipList = null; private int max2Process = Integer.MAX_VALUE; private int processed = 0; - /** - * Set the force processing property. If true, existing canvas - * metadata will be replaced. - * @param force - */ + // used to check for existing canvas dimension + private static final String IIIF_WIDTH_METADATA = "iiif.image.width"; + @Override public void setForceProcessing(boolean force) { forceProcessing = force; } - /** - * Set whether to output messages during processing. - * @param quiet - */ @Override public void setIsQuiet(boolean quiet) { isQuiet = quiet; } - /** - * Set the maximum number of items to process. - * @param max2Process - */ @Override public void setMax2Process(int max2Process) { this.max2Process = max2Process; } - /** - * Set dso identifiers to skip. - * @param skipList - */ @Override public void setSkipList(List skipList) { this.skipList = skipList; } - - /** - * Set IIIF canvas dimensions on all IIIF items in a community and its - * sub-communities. - * @param context - * @param community - * @throws Exception - */ @Override - public void processCommunity(Context context, Community community) throws Exception { + public int processCommunity(Context context, Community community) throws Exception { if (!inSkipList(community.getHandle())) { List subcommunities = community.getSubcommunities(); for (Community subcommunity : subcommunities) { @@ -108,34 +88,24 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic processCollection(context, collection); } } + return processed; } - /** - * Set IIIF canvas dimensions on all IIIF items in a collection. - * @param context - * @param collection - * @throws Exception - */ @Override - public void processCollection(Context context, Collection collection) throws Exception { + public int processCollection(Context context, Collection collection) throws Exception { if (!inSkipList(collection.getHandle())) { Iterator itemIterator = itemService.findAllByCollection(context, collection); while (itemIterator.hasNext() && processed < max2Process) { processItem(context, itemIterator.next()); } } + return processed; } - /** - * Set IIIF canvas dimensions for an item. - * @param context - * @param item - * @throws Exception - */ @Override public void processItem(Context context, Item item) throws Exception { if (!inSkipList(item.getHandle())) { - boolean isIIIFItem = Utils.isIIIFItem(item); + boolean isIIIFItem = IIIFSharedUtils.isIIIFItem(item); if (isIIIFItem) { if (processItemBundles(context, item)) { ++processed; @@ -152,7 +122,7 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic * @throws Exception */ private boolean processItemBundles(Context context, Item item) throws Exception { - List bundles = Utils.getIIIFBundles(item); + List bundles = IIIFSharedUtils.getIIIFBundles(item); boolean done = false; for (Bundle bundle : bundles) { List bitstreams = bundle.getBitstreams(); @@ -161,8 +131,6 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic } } if (done) { - // update the item - // itemService.update(context, item); if (!isQuiet) { System.out.println("Updated canvas metadata for item: " + item.getID()); } @@ -172,15 +140,10 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic } /** -<<<<<<< HEAD - * Sets the IIIF height and width metadata for all images. If width metadata already exists, - * the bitstream is processed only if forceProcessing is true. -======= * Gets image height and width for the bitstream. For jp2 images, height and width are * obtained from the IIIF image server. For other formats supported by ImageIO these values * are read from the actual DSpace bitstream content. If bitstream width metadata already exists, * the bitstream is processed when forceProcessing is true. ->>>>>>> aa0840668aca847c57410c2c9be1cd551c0ae841 * @param context * @param bitstream * @return @@ -222,24 +185,19 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic * @param dims * @return */ - private boolean setBitstreamMetadata(Context context, Bitstream bitstream, int[] dims) { - try { - dSpaceObjectService.clearMetadata(context, bitstream, "iiif", + private boolean setBitstreamMetadata(Context context, Bitstream bitstream, int[] dims) throws Exception { + dSpaceObjectService.clearMetadata(context, bitstream, "iiif", "image", "width", Item.ANY); - dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", + dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", "image", "width", Item.ANY, String.valueOf(dims[0])); - dSpaceObjectService.clearMetadata(context, bitstream, "iiif", + dSpaceObjectService.clearMetadata(context, bitstream, "iiif", "image", "height", Item.ANY); - dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", + dSpaceObjectService.setMetadataSingleValue(context, bitstream, "iiif", "image", "height", Item.ANY, String.valueOf(dims[1])); - if (!isQuiet) { - System.out.println("Added IIIF canvas metadata to bitstream: " + bitstream.getID()); - } - return true; - } catch (SQLException e) { - System.out.println("Unable to update metadata: " + e.getMessage()); - return false; + if (!isQuiet) { + System.out.println("Added IIIF canvas metadata to bitstream: " + bitstream.getID()); } + return true; } /** diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java index 9cc6e5677d..217f6ef3b8 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java @@ -13,6 +13,11 @@ import java.awt.image.BufferedImage; import java.io.InputStream; import javax.imageio.ImageIO; +/** + * Reads and return height and width dimensions for image bitstreams. + * + * @author Michael Spalti mspalti@willamette.edu + */ public class ImageDimensionReader { private ImageDimensionReader() {} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java index 5620585f1c..3245ade1ca 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java @@ -7,6 +7,11 @@ */ package org.dspace.app.canvasdimension; +/** + * Utilities for IIIF canvas dimension processing. + * + * @author Michael Spalti mspalti@willamette.edu + */ public class Util { private Util() {} diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java index ab2d385367..75dad46c80 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java @@ -10,6 +10,11 @@ package org.dspace.app.canvasdimension.factory; import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.services.factory.DSpaceServicesFactory; +/** + * Factory for the image dimension service. + * + * @author Michael Spalti mspalti@willamette.edu + */ public abstract class IIIFCanvasDimensionServiceFactory { public static IIIFCanvasDimensionServiceFactory getInstance() { diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java index da69a33990..abe44f540d 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java @@ -10,9 +10,14 @@ package org.dspace.app.canvasdimension.factory; import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; import org.springframework.beans.factory.annotation.Autowired; +/** + * Factory for the image dimension service. + * + * @author Michael Spalti mspalti@willamette.edu + */ public class IIIFCanvasDimensionServiceFactoryImpl extends IIIFCanvasDimensionServiceFactory { - @Autowired(required = true) + @Autowired() private IIIFCanvasDimensionService iiifCanvasDimensionService; @Override diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java index 0a85a136e4..86e9b91623 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java @@ -9,8 +9,16 @@ package org.dspace.app.canvasdimension.service; import org.dspace.content.Bitstream; +/** + * @author Michael Spalti mspalti@willamette.edu + */ public interface IIIFApiQueryService { + /** + * Returns array with canvas height and width + * @param bitstream + * @return + */ int[] getImageDimensions(Bitstream bitstream); } diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java index 43657c0dff..ba3840b20b 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java +++ b/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java @@ -16,18 +16,54 @@ import org.dspace.core.Context; public interface IIIFCanvasDimensionService { - void processCommunity(Context context, Community community) throws Exception; + /** + * Set IIIF canvas dimensions on all IIIF items in a community and its + * sub-communities. + * @param context + * @param community + * @throws Exception + */ + int processCommunity(Context context, Community community) throws Exception; - void processCollection(Context context, Collection collection) throws Exception; + /** + * Set IIIF canvas dimensions on all IIIF items in a collection. + * @param context + * @param collection + * @throws Exception + */ + int processCollection(Context context, Collection collection) throws Exception; + /** + * Set IIIF canvas dimensions for an item. + * @param context + * @param item + * @throws Exception + */ void processItem(Context context, Item item) throws Exception; + /** + * Set the force processing property. If true, existing canvas + * metadata will be replaced. + * @param force + */ void setForceProcessing(boolean force); + /** + * Set whether to output messages during processing. + * @param quiet + */ void setIsQuiet(boolean quiet); + /** + * Set the maximum number of items to process. + * @param max2Process + */ void setMax2Process(int max2Process); + /** + * Set dso identifiers to skip. + * @param skipList + */ void setSkipList(List skipList); } diff --git a/dspace-api/src/main/java/org/dspace/iiif/Utils.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFSharedUtils.java similarity index 90% rename from dspace-api/src/main/java/org/dspace/iiif/Utils.java rename to dspace-api/src/main/java/org/dspace/iiif/IIIFSharedUtils.java index eaadd256a3..78f72c882c 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/Utils.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFSharedUtils.java @@ -17,15 +17,20 @@ import org.dspace.content.Item; import org.dspace.core.Constants; import org.dspace.license.CreativeCommonsServiceImpl; -public class Utils { +/** + * Shared utilities for IIIF processing. + * + * @author Michael Spalti mspalti@willamette.edu + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class IIIFSharedUtils { // metadata used to enable the iiif features on the item public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled"; - private static final String IIIF_WIDTH_METADATA = "iiif.image.width"; // The DSpace bundle for other content related to item. protected static final String OTHER_CONTENT_BUNDLE = "OtherContent"; - private Utils() {} + private IIIFSharedUtils() {} public static boolean isIIIFItem(Item item) { return item.getMetadata().stream().filter(m -> m.getMetadataField().toString('.') @@ -45,7 +50,7 @@ public class Utils { boolean iiif = isIIIFEnabled(item); List bundles = new ArrayList<>(); if (iiif) { - bundles = item.getBundles().stream().filter(Utils::isIIIFBundle).collect(Collectors.toList()); + bundles = item.getBundles().stream().filter(IIIFSharedUtils::isIIIFBundle).collect(Collectors.toList()); } return bundles; } diff --git a/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java b/dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java similarity index 91% rename from dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java rename to dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java index 14c1bbe57d..7d5bc48e91 100644 --- a/dspace-api/src/test/java/org/dspace/app/canvasdimensions/CanvasDimensionsIT.java +++ b/dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java @@ -5,12 +5,15 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimensions; +package org.dspace.app.canvasdimension; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.PrintStream; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.BitstreamBuilder; @@ -21,6 +24,7 @@ import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -40,8 +44,14 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { private final static String METADATA_IIIF_HEIGHT = "iiif.image.height"; private final static String METADATA_IIIF_WIDTH = "iiif.image.width"; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + @Before public void setup() throws IOException { + + System.setOut(new PrintStream(outContent)); + context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -60,6 +70,13 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); } + @After + @Override + public void destroy() throws Exception { + System.setOut(originalOut); + super.destroy(); + } + @Test public void processItemNoForce() throws Exception { context.turnOffAuthorisationSystem(); @@ -305,16 +322,22 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.turnOffAuthorisationSystem(); // Create a new Item iiifItem = ItemBuilder.createItem(context, col1) - .withTitle("Test Item") + .withTitle("Test Item 1") .withIssueDate("2017-10-17") .enableIIIF() .build(); - // Second item so we can test max2process - iiifItem2 = ItemBuilder.createItem(context, col2) - .withTitle("Test Item") + // Second item + iiifItem2 = ItemBuilder.createItem(context, col1) + .withTitle("Test Item 2") .withIssueDate("2017-10-17") .enableIIIF() .build(); + // Third item so we can test max2process + iiifItem3 = ItemBuilder.createItem(context, col1) + .withTitle("Test Item3") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); // Add jpeg image bitstream (300 x 200) InputStream input = this.getClass().getResourceAsStream("cat.jpg"); @@ -333,27 +356,22 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { .withIIIFCanvasWidth(100) .withIIIFCanvasHeight(100) .build(); + input = this.getClass().getResourceAsStream("cat.jpg"); + Bitstream bitstream3 = BitstreamBuilder + .createBitstream(context, iiifItem3, input) + .withName("Bitstream3.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); context.restoreAuthSystemState(); String handle = parentCommunity.getHandle(); - execCanvasScriptWithMaxRecsOne(handle); - // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 - assertTrue(bitstream.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) - .anyMatch(m -> m.getValue().contentEquals("400"))); - assertTrue(bitstream.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) - .anyMatch(m -> m.getValue().contentEquals("600"))); - // Second bitstream should be unchanged - assertTrue(bitstream2.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) - .anyMatch(m -> m.getValue().contentEquals("100"))); - assertTrue(bitstream2.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) - .anyMatch(m -> m.getValue().contentEquals("100"))); - + execCanvasScriptWithMaxRecs(handle); + // check System.out for number of items processed. + assertEquals("2 IIIF items were processed.\n", outContent.toString()); } @Test @@ -410,7 +428,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { String handle = parentCommunity.getHandle(); - execCanvasScriptWithSkipList(handle, col2.getID().toString() + ", " + col3.getID().toString()); + execCanvasScriptWithSkipList(handle, col2.getHandle() + "," + col3.getHandle()); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -473,7 +491,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { String handle = parentCommunity.getHandle(); - execCanvasScriptWithSkipList(handle, col2.getID().toString()); + execCanvasScriptWithSkipList(handle, col2.getHandle()); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -481,7 +499,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) .anyMatch(m -> m.getValue().contentEquals("600"))); - // Second bitstream should be unchanged because its within a skipped collection + // Second bitstream should be unchanged because it's inside a skipped collection assertTrue(bitstream2.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) .anyMatch(m -> m.getValue().contentEquals("100"))); @@ -499,8 +517,9 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-f"); } - private void execCanvasScriptWithMaxRecsOne(String handle) throws Exception { - runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-m", "1", "-f"); + private void execCanvasScriptWithMaxRecs(String handle) throws Exception { + // maximum 2 + runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-m", "2", "-f", "-q"); } private void execCanvasScriptWithSkipList(String handle, String skip) throws Exception { diff --git a/dspace-api/src/test/resources/org/dspace/app/canvasdimensions/cat.jpg b/dspace-api/src/test/resources/org/dspace/app/canvasdimension/cat.jpg similarity index 100% rename from dspace-api/src/test/resources/org/dspace/app/canvasdimensions/cat.jpg rename to dspace-api/src/test/resources/org/dspace/app/canvasdimension/cat.jpg diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java index 9395c8db89..7bb723ea65 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/IIIFServiceFacade.java @@ -7,8 +7,6 @@ */ package org.dspace.app.iiif; -import static org.dspace.iiif.Utils.isIIIFEnabled; - import java.sql.SQLException; import java.util.UUID; @@ -79,7 +77,7 @@ public class IIIFServiceFacade { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - if (item == null || !isIIIFEnabled(item)) { + if (item == null || !utils.isIIIFEnabled(item)) { throw new ResourceNotFoundException("IIIF manifest for id " + id + " not found"); } return manifestService.getManifest(item, context); 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 a3c90fbf8c..794fbb46ca 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 @@ -7,8 +7,6 @@ */ package org.dspace.app.iiif.service; -import static org.dspace.iiif.Utils.getIIIFBundles; - import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -143,7 +141,7 @@ public class ManifestService extends AbstractResourceService { * @param manifestId the generated manifestId */ private void addRanges(Context context, Item item, String manifestId) { - List bundles = getIIIFBundles(item); + List bundles = utils.getIIIFBundles(item); RangeGenerator root = new RangeGenerator(rangeService); root.setLabel(I18nUtil.getMessage("iiif.toc.root-label")); root.setIdentifier(manifestId + "/range/r0"); @@ -333,7 +331,7 @@ public class ManifestService extends AbstractResourceService { * @param context DSpace context */ private void addRendering(Item item, Context context) { - List bundles = getIIIFBundles(item); + List bundles = utils.getIIIFBundles(item); for (Bundle bundle : bundles) { List bitstreams = bundle.getBitstreams(); for (Bitstream bitstream : bitstreams) { 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 81c77159ab..2296e99111 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 @@ -7,8 +7,6 @@ */ package org.dspace.app.iiif.service.utils; -import static org.dspace.iiif.Utils.getIIIFBundles; - import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -30,6 +28,7 @@ import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.BitstreamService; import org.dspace.core.Context; +import org.dspace.iiif.IIIFSharedUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -76,59 +75,16 @@ public class IIIFUtils { @Autowired protected BitstreamService bitstreamService; - /** -<<<<<<< HEAD - * This method returns the bundles holding IIIF resources if any. - * If there is no IIIF content available an empty bundle list is returned. - * @param item the DSpace item - * - * @return list of DSpace bundles with IIIF content - */ + public List getIIIFBundles(Item item) { - boolean iiif = isIIIFEnabled(item); - List bundles = new ArrayList<>(); - if (iiif) { - bundles = item.getBundles().stream().filter(b -> isIIIFBundle(b)).collect(Collectors.toList()); - } - return bundles; + return IIIFSharedUtils.getIIIFBundles(item); } - /** - * This method verify if the IIIF feature is enabled on the item or parent collection. - * - * @param item the dspace item - * @return true if the item supports IIIF - */ public boolean isIIIFEnabled(Item item) { - return item.getOwningCollection().getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")) - || item.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .anyMatch(m -> m.getValue().equalsIgnoreCase("true") || - m.getValue().equalsIgnoreCase("yes")); + return IIIFSharedUtils.isIIIFEnabled(item); } /** - * Utility method to check is a bundle can contain bitstreams to use as IIIF - * resources - * - * @param b the DSpace bundle to check - * @return true if the bundle can contain bitstreams to use as IIIF resources - */ - private boolean isIIIFBundle(Bundle b) { - return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME, - Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL", - "BRANDED_PREVIEW", "TEXT", OTHER_CONTENT_BUNDLE) - && b.getMetadata().stream() - .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) - .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); - } - - /** -======= ->>>>>>> aa0840668aca847c57410c2c9be1cd551c0ae841 * Return all the bitstreams in the item to be used as IIIF resources * * @param context the DSpace Context @@ -138,7 +94,7 @@ public class IIIFUtils { */ public List getIIIFBitstreams(Context context, Item item) { List bitstreams = new ArrayList(); - for (Bundle bnd : getIIIFBundles(item)) { + for (Bundle bnd : IIIFSharedUtils.getIIIFBundles(item)) { bitstreams .addAll(getIIIFBitstreams(context, bnd)); } diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index e8f5a9b164..2712ad21d0 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -10,7 +10,6 @@ - diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 886929f026..c03d20d6c6 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -3,7 +3,6 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true"> - @@ -17,9 +16,6 @@ - - - diff --git a/dspace/config/spring/api/iiif-processing.xml b/dspace/config/spring/api/iiif-processing.xml new file mode 100644 index 0000000000..bfe06de26e --- /dev/null +++ b/dspace/config/spring/api/iiif-processing.xml @@ -0,0 +1,10 @@ + + + + + + + + From 7f42095f1994d72100bf4cdb31521acba537a27f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 16 Dec 2021 13:28:10 +0100 Subject: [PATCH 0571/1254] added config hal link to SubmissionAccessOption rest object and refactored converter --- .../dspace/app/util/SubmissionStepConfig.java | 1 + .../SubmissionAccessOptionConverter.java | 39 +++++++++++++++++-- .../link/SubmissionSectionHalLinkFactory.java | 31 +++++++-------- .../model/SubmissionAccessOptionRest.java | 7 ++-- ...ubmissionAccessOptionRestRepositoryIT.java | 34 ++++++++-------- .../matcher/AccessConditionOptionMatcher.java | 24 ++++++++---- 6 files changed, 86 insertions(+), 50 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 7cd9dacd03..5506b3c23f 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 @@ -30,6 +30,7 @@ public class SubmissionStepConfig implements Serializable { public static final String INPUT_FORM_STEP_NAME = "submission-form"; public static final String UPLOAD_STEP_NAME = "upload"; + public static final String ACCESS_CONDITION_STEP_NAME = "accessCondition"; /* * The identifier for the Select Collection step diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java index 994e9eb5e6..569da0aab6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java @@ -6,9 +6,15 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.converter; +import java.text.ParseException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.AccessConditionOptionRest; import org.dspace.app.rest.model.SubmissionAccessOptionRest; import org.dspace.app.rest.projection.Projection; import org.dspace.submit.model.AccessConditionConfiguration; +import org.dspace.submit.model.AccessConditionOption; +import org.dspace.util.DateMathParser; import org.springframework.stereotype.Component; /** @@ -21,12 +27,37 @@ import org.springframework.stereotype.Component; public class SubmissionAccessOptionConverter implements DSpaceConverter { + DateMathParser dateMathParser = new DateMathParser(); + @Override - public SubmissionAccessOptionRest convert(AccessConditionConfiguration obj, Projection projection) { + public SubmissionAccessOptionRest convert(AccessConditionConfiguration config, Projection projection) { SubmissionAccessOptionRest model = new SubmissionAccessOptionRest(); - model.setId(obj.getName()); - model.setDiscoverable(obj.getDiscoverable()); - model.setAccessConditionOptions(obj.getOptions()); + model.setId(config.getName()); + model.setDiscoverable(config.getDiscoverable()); + model.setProjection(projection); + for (AccessConditionOption option : config.getOptions()) { + AccessConditionOptionRest optionRest = new AccessConditionOptionRest(); + optionRest.setHasStartDate(option.getHasStartDate()); + optionRest.setHasEndDate(option.getHasEndDate()); + if (StringUtils.isNotBlank(option.getStartDateLimit())) { + try { + optionRest.setMaxStartDate(dateMathParser.parseMath(option.getStartDateLimit())); + } catch (ParseException e) { + throw new IllegalStateException("Wrong start date limit configuration for the access condition " + + "option named " + option.getName()); + } + } + if (StringUtils.isNotBlank(option.getEndDateLimit())) { + try { + optionRest.setMaxEndDate(dateMathParser.parseMath(option.getEndDateLimit())); + } catch (ParseException e) { + throw new IllegalStateException("Wrong end date limit configuration for the access condition " + + "option named " + option.getName()); + } + } + optionRest.setName(option.getName()); + model.getAccessConditionOptions().add(optionRest); + } return model; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/SubmissionSectionHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/SubmissionSectionHalLinkFactory.java index 50fd250382..4cce63ed69 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/SubmissionSectionHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/SubmissionSectionHalLinkFactory.java @@ -6,13 +6,14 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.link; - +import static org.dspace.app.rest.model.SubmissionFormRest.NAME_LINK_ON_PANEL; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; import java.util.LinkedList; import org.atteo.evo.inflector.English; import org.dspace.app.rest.RestResourceController; +import org.dspace.app.rest.model.SubmissionAccessOptionRest; import org.dspace.app.rest.model.SubmissionFormRest; import org.dspace.app.rest.model.SubmissionSectionRest; import org.dspace.app.rest.model.SubmissionUploadRest; @@ -36,26 +37,22 @@ public class SubmissionSectionHalLinkFactory extends HalLinkFactory list, SubmissionSectionRest sd, String category, String name) { + UriComponentsBuilder uriComponentsBuilder = linkTo(getMethodOn(category, name) + .findRel(null, null, category, English.plural(name), sd.getId(), "", null, null)) + .toUriComponentsBuilder(); + String uribuilder = uriComponentsBuilder.build().toString(); + list.add(buildLink(NAME_LINK_ON_PANEL, uribuilder.substring(0, uribuilder.lastIndexOf("/")))); } protected Class getControllerClass() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java index 209f7a6c8f..c45ba657cd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java @@ -12,7 +12,6 @@ import java.util.Objects; import com.fasterxml.jackson.annotation.JsonIgnore; import org.dspace.app.rest.RestResourceController; -import org.dspace.submit.model.AccessConditionOption; /** * The Access Condition Section Configuration REST Resource @@ -31,7 +30,7 @@ public class SubmissionAccessOptionRest extends BaseObjectRest { private Boolean discoverable; - private List accessConditionOptions; + private List accessConditionOptions; public String getId() { return id; @@ -49,14 +48,14 @@ public class SubmissionAccessOptionRest extends BaseObjectRest { this.discoverable = discoverable; } - public List getAccessConditionOptions() { + public List getAccessConditionOptions() { if (Objects.isNull(accessConditionOptions)) { accessConditionOptions = new ArrayList<>(); } return accessConditionOptions; } - public void setAccessConditionOptions(List accessConditionOptions) { + public void setAccessConditionOptions(List accessConditionOptions) { this.accessConditionOptions = accessConditionOptions; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java index 57af4c01c5..4fb2696dc3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java @@ -41,11 +41,11 @@ public class SubmissionAccessOptionRestRepositoryIT extends AbstractControllerIn .andExpect(jsonPath("$.discoverable", is(true))) .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( AccessConditionOptionMatcher.matchAccessConditionOption( - "openaccess","Anonymous", false , false, null, null), + "openaccess", false , false, null, null), AccessConditionOptionMatcher.matchAccessConditionOption( - "embargo","Anonymous", true , false, "+36MONTHS", null), + "embargo", true , false, "+36MONTHS", null), AccessConditionOptionMatcher.matchAccessConditionOption( - "administrator","Administrator", false , false, null, null)) + "administrator", false , false, null, null)) )) .andExpect(jsonPath("$.type", is("submissionaccessoption"))); @@ -56,13 +56,13 @@ public class SubmissionAccessOptionRestRepositoryIT extends AbstractControllerIn .andExpect(jsonPath("$.id", is("defaultAC"))) .andExpect(jsonPath("$.discoverable", is(true))) .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( - AccessConditionOptionMatcher.matchAccessConditionOption( - "openaccess","Anonymous", false , false, null, null), - AccessConditionOptionMatcher.matchAccessConditionOption( - "embargo","Anonymous", true , false, "+36MONTHS", null), - AccessConditionOptionMatcher.matchAccessConditionOption( - "administrator","Administrator", false , false, null, null)) - )) + AccessConditionOptionMatcher.matchAccessConditionOption( + "openaccess", false , false, null, null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "embargo", true , false, "+36MONTHS", null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "administrator", false , false, null, null)) + )) .andExpect(jsonPath("$.type", is("submissionaccessoption"))); getClient().perform(get("/api/config/submissionaccessoptions/defaultAC")) @@ -70,13 +70,13 @@ public class SubmissionAccessOptionRestRepositoryIT extends AbstractControllerIn .andExpect(jsonPath("$.id", is("defaultAC"))) .andExpect(jsonPath("$.discoverable", is(true))) .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( - AccessConditionOptionMatcher.matchAccessConditionOption( - "openaccess","Anonymous", false , false, null, null), - AccessConditionOptionMatcher.matchAccessConditionOption( - "embargo","Anonymous", true , false, "+36MONTHS", null), - AccessConditionOptionMatcher.matchAccessConditionOption( - "administrator","Administrator", false , false, null, null)) - )) + AccessConditionOptionMatcher.matchAccessConditionOption( + "openaccess", false , false, null, null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "embargo", true , false, "+36MONTHS", null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "administrator", false , false, null, null)) + )) .andExpect(jsonPath("$.type", is("submissionaccessoption"))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AccessConditionOptionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AccessConditionOptionMatcher.java index e28a2e224d..23d11b62dd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AccessConditionOptionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AccessConditionOptionMatcher.java @@ -6,11 +6,15 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.matcher; - import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import java.util.Objects; + +import org.apache.commons.lang.StringUtils; import org.hamcrest.Matcher; /** @@ -23,15 +27,19 @@ public class AccessConditionOptionMatcher { private AccessConditionOptionMatcher() {} - public static Matcher matchAccessConditionOption(String name, String groupName, - boolean hasStartDate, boolean hasEndDate, String startDateLimit, String endDateLimit) { + public static Matcher matchAccessConditionOption(String name, + Boolean hasStartDate, Boolean hasEndDate, String maxStartDate, String maxEndDate) { return allOf( hasJsonPath("$.name", is(name)), - hasJsonPath("$.groupName", is(groupName)), - hasJsonPath("$.hasStartDate", is(hasStartDate)), - hasJsonPath("$.hasEndDate", is(hasEndDate)), - hasJsonPath("$.startDateLimit", is(startDateLimit)), - hasJsonPath("$.endDateLimit", is(endDateLimit)) + Objects.nonNull(hasStartDate) ? hasJsonPath("$.hasStartDate", is(hasStartDate)) + : hasNoJsonPath("$.hasStartDate"), + Objects.nonNull(hasEndDate) ? hasJsonPath("$.hasEndDate", is(hasEndDate)) + : hasNoJsonPath("$.hasEndDate"), + StringUtils.isNotBlank(maxStartDate) ? hasJsonPath("$.maxStartDate", notNullValue()) + : hasNoJsonPath("$.maxStartDate"), + StringUtils.isNotBlank(maxEndDate) ? hasJsonPath("$.maxEndDate", notNullValue()) + : hasNoJsonPath("$.maxEndDate") ); } + } \ No newline at end of file From 27283a1eb23a283ab242fa9996b9fa097616d832 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Thu, 16 Dec 2021 15:04:10 +0100 Subject: [PATCH 0572/1254] Making default qualdrop non mandatory again --- dspace/config/submission-forms.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index cc72057988..76dc1ad88d 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -150,7 +150,7 @@ If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. - test for requred + From 665c4942b191e088c5c111a4e071ca25a05f5dfa Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 16 Dec 2021 18:36:58 +0100 Subject: [PATCH 0573/1254] changed discoverable property to make it more readable --- .../model/AccessConditionConfiguration.java | 10 ++++----- .../config/spring/api/access-conditions.xml | 4 ++-- .../SubmissionAccessOptionConverter.java | 2 +- .../model/SubmissionAccessOptionRest.java | 10 ++++----- ...tionDiscoverableReplacePatchOperation.java | 2 +- ...ubmissionAccessOptionRestRepositoryIT.java | 22 ++++++++++++++++--- .../config/spring/api/access-conditions.xml | 2 +- 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfiguration.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfiguration.java index f3c1ef13e1..3edefb4aa4 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfiguration.java @@ -14,7 +14,7 @@ import java.util.List; public class AccessConditionConfiguration { private String name; - private Boolean discoverable; + private Boolean canChangeDiscoverable; private List options; public String getName() { @@ -25,12 +25,12 @@ public class AccessConditionConfiguration { this.name = name; } - public Boolean getDiscoverable() { - return discoverable; + public Boolean getCanChangeDiscoverable() { + return canChangeDiscoverable; } - public void setDiscoverable(Boolean discoverable) { - this.discoverable = discoverable; + public void setCanChangeDiscoverable(Boolean canChangeDiscoverable) { + this.canChangeDiscoverable = canChangeDiscoverable; } public List getOptions() { diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml index a879f1be91..fac7fcc1af 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml @@ -71,7 +71,7 @@ - + @@ -83,7 +83,7 @@ - + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java index 569da0aab6..f46f596dba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionAccessOptionConverter.java @@ -33,7 +33,7 @@ public class SubmissionAccessOptionConverter public SubmissionAccessOptionRest convert(AccessConditionConfiguration config, Projection projection) { SubmissionAccessOptionRest model = new SubmissionAccessOptionRest(); model.setId(config.getName()); - model.setDiscoverable(config.getDiscoverable()); + model.setCanChangeDiscoverable(config.getCanChangeDiscoverable()); model.setProjection(projection); for (AccessConditionOption option : config.getOptions()) { AccessConditionOptionRest optionRest = new AccessConditionOptionRest(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java index c45ba657cd..08f4c82f43 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionAccessOptionRest.java @@ -28,7 +28,7 @@ public class SubmissionAccessOptionRest extends BaseObjectRest { private String id; - private Boolean discoverable; + private Boolean canChangeDiscoverable; private List accessConditionOptions; @@ -40,12 +40,12 @@ public class SubmissionAccessOptionRest extends BaseObjectRest { this.id = id; } - public Boolean getDiscoverable() { - return discoverable; + public Boolean getCanChangeDiscoverable() { + return canChangeDiscoverable; } - public void setDiscoverable(Boolean discoverable) { - this.discoverable = discoverable; + public void setCanChangeDiscoverable(Boolean canChangeDiscoverable) { + this.canChangeDiscoverable = canChangeDiscoverable; } public List getAccessConditionOptions() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java index 880c59bb2d..58c638bdeb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionDiscoverableReplacePatchOperation.java @@ -36,7 +36,7 @@ public class AccessConditionDiscoverableReplacePatchOperation extends ReplacePat String stepId = (String) currentRequest.getAttribute("accessConditionSectionId"); AccessConditionConfiguration configuration = accessConditionConfigurationService.getMap().get(stepId); - if (Objects.isNull(configuration) || !configuration.getDiscoverable().booleanValue()) { + if (Objects.isNull(configuration) || !configuration.getCanChangeDiscoverable().booleanValue()) { throw new UnprocessableEntityException("The current access configurations does not allow" + " the user to specify the visibility of the item"); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java index 4fb2696dc3..b4ad9f58f2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionAccessOptionRestRepositoryIT.java @@ -38,7 +38,7 @@ public class SubmissionAccessOptionRestRepositoryIT extends AbstractControllerIn getClient(tokenAdmin).perform(get("/api/config/submissionaccessoptions/defaultAC")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is("defaultAC"))) - .andExpect(jsonPath("$.discoverable", is(true))) + .andExpect(jsonPath("$.canChangeDiscoverable", is(true))) .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( AccessConditionOptionMatcher.matchAccessConditionOption( "openaccess", false , false, null, null), @@ -54,7 +54,7 @@ public class SubmissionAccessOptionRestRepositoryIT extends AbstractControllerIn getClient(tokenEPerson).perform(get("/api/config/submissionaccessoptions/defaultAC")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is("defaultAC"))) - .andExpect(jsonPath("$.discoverable", is(true))) + .andExpect(jsonPath("$.canChangeDiscoverable", is(true))) .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( AccessConditionOptionMatcher.matchAccessConditionOption( "openaccess", false , false, null, null), @@ -68,7 +68,7 @@ public class SubmissionAccessOptionRestRepositoryIT extends AbstractControllerIn getClient().perform(get("/api/config/submissionaccessoptions/defaultAC")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is("defaultAC"))) - .andExpect(jsonPath("$.discoverable", is(true))) + .andExpect(jsonPath("$.canChangeDiscoverable", is(true))) .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( AccessConditionOptionMatcher.matchAccessConditionOption( "openaccess", false , false, null, null), @@ -80,6 +80,22 @@ public class SubmissionAccessOptionRestRepositoryIT extends AbstractControllerIn .andExpect(jsonPath("$.type", is("submissionaccessoption"))); } + @Test + public void findOneCanNotChangeDiscoverableTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/config/submissionaccessoptions/notDiscoverable")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("notDiscoverable"))) + .andExpect(jsonPath("$.canChangeDiscoverable", is(false))) + .andExpect(jsonPath("$.accessConditionOptions", Matchers.containsInAnyOrder( + AccessConditionOptionMatcher.matchAccessConditionOption( + "embargo", true , false, "+36MONTHS", null), + AccessConditionOptionMatcher.matchAccessConditionOption( + "administrator", false , false, null, null)) + )) + .andExpect(jsonPath("$.type", is("submissionaccessoption"))); + } + @Test public void findOneNotFoundTest() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); diff --git a/dspace/config/spring/api/access-conditions.xml b/dspace/config/spring/api/access-conditions.xml index 9049ea35a9..b1926301ae 100644 --- a/dspace/config/spring/api/access-conditions.xml +++ b/dspace/config/spring/api/access-conditions.xml @@ -71,7 +71,7 @@ - + From b8a81ac824e9da17cb84962984d34255a013b746 Mon Sep 17 00:00:00 2001 From: April Herron Date: Tue, 16 Nov 2021 15:25:26 -0500 Subject: [PATCH 0574/1254] DS-3981 Improve IndexClient usage & options --- .../org/dspace/discovery/IndexClient.java | 15 ++- .../dspace/discovery/IndexClientOptions.java | 13 +-- .../org/dspace/discovery/IndexingService.java | 5 +- .../org/dspace/discovery/SolrServiceImpl.java | 96 ++++++++++--------- .../dspace/storage/rdbms/DatabaseUtils.java | 2 +- 5 files changed, 69 insertions(+), 62 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java index 4e6fa16177..fcb3e79d1d 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -61,13 +61,14 @@ public class IndexClient extends DSpaceRunnable 1 ? "s" : "") + " in " + seconds + " seconds"); } else if (indexClientOptions == IndexClientOptions.UPDATE || indexClientOptions == IndexClientOptions.UPDATEANDSPELLCHECK) { - handler.logInfo("Updating and Cleaning Index"); - indexer.cleanIndex(false); + handler.logInfo("Updating Index"); indexer.updateIndex(context, false); if (indexClientOptions == IndexClientOptions.UPDATEANDSPELLCHECK) { checkRebuildSpellCheck(commandLine, indexer); } } else if (indexClientOptions == IndexClientOptions.FORCEUPDATE || indexClientOptions == IndexClientOptions.FORCEUPDATEANDSPELLCHECK) { - handler.logInfo("Updating and Cleaning Index"); - indexer.cleanIndex(true); + handler.logInfo("Updating Index"); indexer.updateIndex(context, true); if (indexClientOptions == IndexClientOptions.FORCEUPDATEANDSPELLCHECK) { checkRebuildSpellCheck(commandLine, indexer); diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java index 4b29fbbf27..62357bd95f 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java @@ -17,7 +17,7 @@ import org.apache.commons.cli.Options; public enum IndexClientOptions { REMOVE, CLEAN, - FORCECLEAN, + DELETE, BUILD, BUILDANDSPELLCHECK, OPTIMIZE, @@ -41,11 +41,9 @@ public enum IndexClientOptions { } else if (commandLine.hasOption("r")) { return IndexClientOptions.REMOVE; } else if (commandLine.hasOption("c")) { - if (commandLine.hasOption("f")) { - return IndexClientOptions.FORCECLEAN; - } else { - return IndexClientOptions.CLEAN; - } + return IndexClientOptions.CLEAN; + } else if (commandLine.hasOption("d")) { + return IndexClientOptions.DELETE; } else if (commandLine.hasOption("b")) { if (commandLine.hasOption("s")) { return IndexClientOptions.BUILDANDSPELLCHECK; @@ -83,6 +81,9 @@ public enum IndexClientOptions { options.addOption("c", "clean", false, "clean existing index removing any documents that no longer exist in the db"); options.getOption("c").setType(boolean.class); + options.addOption("d", "delete", false, + "delete all records from existing index"); + options.getOption("d").setType(boolean.class); options.addOption("b", "build", false, "(re)build index, wiping out current one if it exists"); options.getOption("b").setType(boolean.class); options.addOption("s", "spellchecker", false, "Rebuild the spellchecker, can be combined with -b and -f."); diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java index 46795d759e..db0329dd67 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingService.java @@ -53,8 +53,9 @@ public interface IndexingService { void updateIndex(Context context, boolean force, String type); - void cleanIndex(boolean force) throws IOException, - SQLException, SearchServiceException; + void cleanIndex() throws IOException, SQLException, SearchServiceException; + + void deleteIndex(); void commit() throws SearchServiceException; 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 84aca869cb..f894553e5d 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -333,17 +333,31 @@ public class SolrServiceImpl implements SearchService, IndexingService { } } + /** + * Removes all documents from the Lucene index + */ + public void deleteIndex() { + try { + final List indexableObjectServices = indexObjectServiceFactory. + getIndexFactories(); + for (IndexFactory indexableObjectService : indexableObjectServices) { + indexableObjectService.deleteAll(); + } + } catch (IOException | SolrServerException e) { + log.error("Error cleaning discovery index: " + e.getMessage(), e); + } + } + /** * Iterates over all documents in the Lucene index and verifies they are in * database, if not, they are removed. * - * @param force whether or not to force a clean index * @throws IOException IO exception * @throws SQLException sql exception * @throws SearchServiceException occurs when something went wrong with querying the solr server */ @Override - public void cleanIndex(boolean force) throws IOException, SQLException, SearchServiceException { + public void cleanIndex() throws IOException, SQLException, SearchServiceException { Context context = new Context(); context.turnOffAuthorisationSystem(); @@ -351,56 +365,48 @@ public class SolrServiceImpl implements SearchService, IndexingService { if (solrSearchCore.getSolr() == null) { return; } - if (force) { - final List indexableObjectServices = indexObjectServiceFactory. - getIndexFactories(); - for (IndexFactory indexableObjectService : indexableObjectServices) { - indexableObjectService.deleteAll(); - } - } else { - // First, we'll just get a count of the total results - SolrQuery countQuery = new SolrQuery("*:*"); - countQuery.setRows(0); // don't actually request any data - // Get the total amount of results - QueryResponse totalResponse = solrSearchCore.getSolr().query(countQuery, - solrSearchCore.REQUEST_METHOD); - long total = totalResponse.getResults().getNumFound(); + // First, we'll just get a count of the total results + SolrQuery countQuery = new SolrQuery("*:*"); + countQuery.setRows(0); // don't actually request any data + // Get the total amount of results + QueryResponse totalResponse = solrSearchCore.getSolr().query(countQuery, + solrSearchCore.REQUEST_METHOD); + long total = totalResponse.getResults().getNumFound(); - int start = 0; - int batch = 100; + int start = 0; + int batch = 100; - // Now get actual Solr Documents in batches - SolrQuery query = new SolrQuery(); - query.setFields(SearchUtils.RESOURCE_UNIQUE_ID, SearchUtils.RESOURCE_ID_FIELD, - SearchUtils.RESOURCE_TYPE_FIELD); - query.addSort(SearchUtils.RESOURCE_UNIQUE_ID, SolrQuery.ORDER.asc); - query.setQuery("*:*"); - query.setRows(batch); - // Keep looping until we hit the total number of Solr docs - while (start < total) { - query.setStart(start); - QueryResponse rsp = solrSearchCore.getSolr().query(query, solrSearchCore.REQUEST_METHOD); - SolrDocumentList docs = rsp.getResults(); + // Now get actual Solr Documents in batches + SolrQuery query = new SolrQuery(); + query.setFields(SearchUtils.RESOURCE_UNIQUE_ID, SearchUtils.RESOURCE_ID_FIELD, + SearchUtils.RESOURCE_TYPE_FIELD); + query.addSort(SearchUtils.RESOURCE_UNIQUE_ID, SolrQuery.ORDER.asc); + query.setQuery("*:*"); + query.setRows(batch); + // Keep looping until we hit the total number of Solr docs + while (start < total) { + query.setStart(start); + QueryResponse rsp = solrSearchCore.getSolr().query(query, solrSearchCore.REQUEST_METHOD); + SolrDocumentList docs = rsp.getResults(); - for (SolrDocument doc : docs) { - String uniqueID = (String) doc.getFieldValue(SearchUtils.RESOURCE_UNIQUE_ID); + for (SolrDocument doc : docs) { + String uniqueID = (String) doc.getFieldValue(SearchUtils.RESOURCE_UNIQUE_ID); - IndexableObject o = findIndexableObject(context, doc); + IndexableObject o = findIndexableObject(context, doc); - if (o == null) { - log.info("Deleting: " + uniqueID); - /* - * Use IndexWriter to delete, its easier to manage - * write.lock - */ - unIndexContent(context, uniqueID); - } else { - log.debug("Keeping: " + o.getUniqueIndexID()); - } + if (o == null) { + log.info("Deleting: " + uniqueID); + /* + * Use IndexWriter to delete, its easier to manage + * write.lock + */ + unIndexContent(context, uniqueID); + } else { + log.debug("Keeping: " + o.getUniqueIndexID()); } - - start += batch; } + + start += batch; } } catch (IOException | SQLException | SolrServerException e) { log.error("Error cleaning discovery index: " + e.getMessage(), e); 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 27f58ec2f6..8835e03104 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 @@ -1327,7 +1327,7 @@ public class DatabaseUtils { // Reindex Discovery completely // Force clean all content - this.indexer.cleanIndex(true); + this.indexer.deleteIndex(); // Recreate the entire index (overwriting existing one) this.indexer.createIndex(context); // Rebuild spell checker (which is based on index) From dbede2b2462a1a78bf711100e60c7e5c41c0e79f Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 18 Dec 2021 12:16:18 +0100 Subject: [PATCH 0575/1254] Bumps log4j dependency from 2.16.0 to 2.17.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2980b9436c..bce40bf8ed 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ 2.3.1 9.4.41.v20210516 - 2.16.0 + 2.17.0 2.0.24 3.17 1.7.25 From f7d02da935fc97dd993d9b92c2a41bef6aa9d74d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 20 Dec 2021 14:59:22 +0100 Subject: [PATCH 0576/1254] rename feature --- .../java/org/dspace/app/rest/StatisticsRestController.java | 4 ---- ...isticsFeature.java => CanViewUsageStatisticsFeature.java} | 5 +++-- 2 files changed, 3 insertions(+), 6 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/{ViewUsageStatisticsFeature.java => CanViewUsageStatisticsFeature.java} (93%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java index 77cae6f596..4c2dc54574 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java @@ -20,7 +20,6 @@ import org.dspace.app.rest.model.hateoas.ViewEventResource; import org.dspace.app.rest.repository.SearchEventRestRepository; import org.dspace.app.rest.repository.StatisticsRestRepository; import org.dspace.app.rest.repository.ViewEventRestRepository; -import org.dspace.app.rest.utils.Utils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ControllerUtils; @@ -39,9 +38,6 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/" + RestAddressableModel.STATISTICS) public class StatisticsRestController implements InitializingBean { - @Autowired - private Utils utils; - @Autowired private DiscoverableEndpointsService discoverableEndpointsService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ViewUsageStatisticsFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanViewUsageStatisticsFeature.java similarity index 93% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ViewUsageStatisticsFeature.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanViewUsageStatisticsFeature.java index a71bc04cf9..95d215418a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ViewUsageStatisticsFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanViewUsageStatisticsFeature.java @@ -31,9 +31,9 @@ import org.springframework.stereotype.Component; * is the object's admin. Otherwise, authorization is granted if the current user can view the object. */ @Component -@AuthorizationFeatureDocumentation(name = ViewUsageStatisticsFeature.NAME, +@AuthorizationFeatureDocumentation(name = CanViewUsageStatisticsFeature.NAME, description = "It can be used to verify if statistics can be viewed") -public class ViewUsageStatisticsFeature implements AuthorizationFeature { +public class CanViewUsageStatisticsFeature implements AuthorizationFeature { public final static String NAME = "canViewUsageStatistics"; @@ -47,6 +47,7 @@ public class ViewUsageStatisticsFeature implements AuthorizationFeature { private Utils utils; @Override + @SuppressWarnings("rawtypes") public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { if (object instanceof SiteRest || object instanceof CommunityRest From f75bdc4818a2d1620fce5b52f2882ddcd33f1488 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 20 Dec 2021 15:06:28 +0100 Subject: [PATCH 0577/1254] refactored statistics UsageRepor security plugin --- ...geReportRestPermissionEvaluatorPlugin.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java index c6d080f533..cf91b5c050 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java @@ -9,6 +9,7 @@ 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; @@ -18,12 +19,12 @@ import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; 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.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -37,6 +38,9 @@ public class UsageReportRestPermissionEvaluatorPlugin extends RestObjectPermissi private static final Logger log = LoggerFactory.getLogger(UsageReportRestPermissionEvaluatorPlugin.class); + @Autowired + private ConfigurationService configurationService; + @Autowired private RequestService requestService; @@ -44,7 +48,7 @@ public class UsageReportRestPermissionEvaluatorPlugin extends RestObjectPermissi private DSpaceObjectUtils dspaceObjectUtil; @Autowired - AuthorizeService authorizeService; + private AuthorizeService authorizeService; @@ -64,32 +68,38 @@ public class UsageReportRestPermissionEvaluatorPlugin extends RestObjectPermissi Request request = requestService.getCurrentRequest(); Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); UUID uuidObject = null; - if (targetId != null) { - if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME, targetType)) { - if (StringUtils.countMatches(targetId.toString(), "_") != 1) { - throw new IllegalArgumentException("Must end in objectUUID_reportId, example: " + - "1911e8a4-6939-490c-b58b-a5d70f8d91fb_TopCountries"); - } - // Get uuid from uuidDSO_reportId pathParam - uuidObject = UUID.fromString(StringUtils.substringBefore(targetId.toString(), "_")); - } else if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME + "search", targetType)) { - // Get uuid from url (selfLink of dso) queryParam - uuidObject = UUID.fromString(StringUtils.substringAfterLast(targetId.toString(), "/")); - } else { - return false; + try { + if (configurationService.getBooleanProperty("usage-statistics.authorization.admin.usage", true)) { + return authorizeService.isAdmin(context); } - try { + if (Objects.nonNull(targetId)) { + if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME, targetType)) { + if (StringUtils.countMatches(targetId.toString(), "_") != 1) { + throw new IllegalArgumentException("Must end in objectUUID_reportId, example: " + + "1911e8a4-6939-490c-b58b-a5d70f8d91fb_TopCountries"); + } + // Get uuid from uuidDSO_reportId pathParam + uuidObject = UUID.fromString(StringUtils.substringBefore(targetId.toString(), "_")); + } else if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME + "search", targetType)) { + // Get uuid from url (selfLink of dso) queryParam + uuidObject = UUID.fromString(StringUtils.substringAfterLast(targetId.toString(), "/")); + } else { + return false; + } + DSpaceObject dso = dspaceObjectUtil.findDSpaceObject(context, uuidObject); - if (dso == null) { - throw new ResourceNotFoundException("No DSO found with this UUID: " + uuidObject); + // If the dso is null then we give permission so we can throw another status code instead + if (Objects.isNull(dso)) { + return true; } + return authorizeService.authorizeActionBoolean(context, dso, restPermission.getDspaceApiActionId()); - } catch (SQLException e) { - log.error(e.getMessage(), e); } + } catch (SQLException e) { + log.error(e.getMessage(), e); } - return true; } return false; } -} + +} \ No newline at end of file From 4dd22eb5f624c3b16ceeb9676a53c57bb0b80faa Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 20 Dec 2021 15:06:49 +0100 Subject: [PATCH 0578/1254] fix failed tests --- .../repository/StatisticsRestRepository.java | 5 +- .../app/rest/StatisticsRestRepositoryIT.java | 162 ++++++++++++++---- 2 files changed, 130 insertions(+), 37 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java index 4aa7572767..09588a6045 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java @@ -27,6 +27,7 @@ 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; @@ -53,7 +54,7 @@ public class StatisticsRestRepository extends DSpaceRestRepository expectedPoints = this.getListOfVisitsPerMonthsPoints(1); // And request that item's TotalVisitsPerMonth stat report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -459,6 +511,15 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes UsageReportMatcher .matchUsageReport(itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, TOTAL_VISITS_PER_MONTH_REPORT_ID, expectedPoints)))); + + // only admin has access + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) + .andExpect(status().isForbidden()); + + getClient().perform( + get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) + .andExpect(status().isUnauthorized()); } @Test @@ -468,7 +529,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes List expectedPoints = this.getListOfVisitsPerMonthsPoints(0); // And request that item's TotalVisitsPerMonth stat report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) // ** THEN ** @@ -503,7 +564,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes List expectedPoints = this.getListOfVisitsPerMonthsPoints(2); // And request that collection's TotalVisitsPerMonth stat report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -534,7 +595,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPoint.setId(bitstreamVisited.getID().toString()); // And request that bitstreams's TotalDownloads stat report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -542,6 +603,15 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes UsageReportMatcher .matchUsageReport(bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, TOTAL_DOWNLOADS_REPORT_ID, Arrays.asList(expectedPoint))))); + + // only admin has access to downloads report + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) + .andExpect(status().isForbidden()); + + getClient().perform( + get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) + .andExpect(status().isUnauthorized()); } @Test @@ -565,7 +635,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPoint.setType("bitstream"); // And request that item's TotalDownloads stat report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) // ** THEN ** @@ -581,7 +651,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** WHEN ** // You don't visit an item's bitstreams // And request that item's TotalDownloads stat report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) // ** THEN ** @@ -594,7 +664,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void TotalDownloadsReport_NotSupportedDSO_Collection() throws Exception { - getClient() + getClient(adminToken) .perform(get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())); } @@ -623,7 +693,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPoint.setLabel("United States"); // And request that collection's TopCountries report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -631,6 +701,15 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes UsageReportMatcher .matchUsageReport(collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, TOP_COUNTRIES_REPORT_ID, Arrays.asList(expectedPoint))))); + + // only admin has access to countries report + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) + .andExpect(status().isForbidden()); + + getClient().perform( + get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) + .andExpect(status().isUnauthorized()); } /** @@ -662,7 +741,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPoint.setLabel("United States"); // And request that collection's TopCountries report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + communityVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -680,7 +759,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** WHEN ** // Item is not visited // And request that item's TopCountries report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -713,7 +792,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPoint.setId("New York"); // And request that item's TopCities report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -721,6 +800,15 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes UsageReportMatcher .matchUsageReport(itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, TOP_CITIES_REPORT_ID, Arrays.asList(expectedPoint))))); + + // only admin has access to cities report + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) + .andExpect(status().isForbidden()); + + getClient().perform( + get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) + .andExpect(status().isUnauthorized()); } /** @@ -756,7 +844,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPoint.setId("New York"); // And request that community's TopCities report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + communityVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -774,7 +862,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** WHEN ** // Collection is not visited // And request that collection's TopCountries report - getClient().perform( + getClient(adminToken).perform( get("/api/statistics/usagereports/" + collectionNotVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) @@ -786,7 +874,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void usagereportsSearch_notProperURI_Exception() throws Exception { - getClient().perform(get("/api/statistics/usagereports/search/object?uri=BadUri")) + getClient(adminToken).perform(get("/api/statistics/usagereports/search/object?uri=BadUri")) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())); } @@ -798,7 +886,8 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void usagereportsSearch_NonExistentUUID_Exception() throws Exception { - getClient().perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + + getClient(adminToken).perform( + get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + "/items/" + UUID.randomUUID())) .andExpect(status().is(HttpStatus.NOT_FOUND.value())); } @@ -859,7 +948,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + "/items/" + itemNotVisitedWithBitstreams.getID())) // ** THEN ** - .andExpect(status().isOk()); + .andExpect(status().isForbidden()); // We request a dso's TotalVisits usage stat report as another logged in eperson and has no read policy for // this user getClient(anotherLoggedInUserToken) @@ -959,7 +1048,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPointCountry.setLabel("United States"); // And request the community usage reports - getClient() + getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + "/communities/" + communityVisited.getID())) // ** THEN ** @@ -983,13 +1072,13 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes public void usageReportsSearch_Collection_NotVisited() throws Exception { // ** WHEN ** // Collection is not visited - +try { UsageReportPointDsoTotalVisitsRest expectedPointTotalVisits = new UsageReportPointDsoTotalVisitsRest(); expectedPointTotalVisits.addValue("views", 0); expectedPointTotalVisits.setType("collection"); expectedPointTotalVisits.setId(collectionNotVisited.getID().toString()); // And request the collection's usage reports - getClient() + getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + "/collections/" + collectionNotVisited.getID())) // ** THEN ** @@ -1008,6 +1097,9 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes TOP_CITIES_REPORT_ID, new ArrayList<>()), UsageReportMatcher.matchUsageReport(collectionNotVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, TOP_COUNTRIES_REPORT_ID, new ArrayList<>())))); + } finally { + System.out.println(""); + } } @Test @@ -1040,7 +1132,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPointCountry.setLabel("United States"); // And request the community usage reports - getClient() + getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + "/items/" + itemVisited.getID())) // ** THEN ** @@ -1133,7 +1225,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes totalDownloadsPoints.add(expectedPointTotalVisitsBit2); // And request the community usage reports - getClient() + getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + "/items/" + itemVisited.getID())) // ** THEN ** @@ -1185,7 +1277,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes expectedPointCountry.setLabel("United States"); // And request the community usage reports - getClient() + getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + "/items/" + bitstreamVisited.getID())) // ** THEN ** From 166f84f16da35e0c22bd983dd7a6b460bb2acf1f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 20 Dec 2021 18:17:17 +0100 Subject: [PATCH 0579/1254] added test to prove bug with array access conditions --- .../rest/WorkspaceItemRestRepositoryIT.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index e4c3c1f3d1..26cf6ada91 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 @@ -6275,12 +6275,6 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .withSubject("ExtraEntry") .build(); - ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(witem.getItem()) - .withPolicyType(TYPE_CUSTOM) - .withName("openaccess") - .build(); - context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -6288,15 +6282,21 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) - .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", - is("openaccess"))) - .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name").doesNotExist()); List addAccessCondition = new ArrayList(); - Map accessCondition = new HashMap(); - accessCondition.put("name", "administrator"); + List> accessConditions = new ArrayList>(); + + Map accessCondition1 = new HashMap(); + accessCondition1.put("name", "administrator"); + accessConditions.add(accessCondition1); + + Map accessCondition2 = new HashMap(); + accessCondition2.put("name", "openaccess"); + accessConditions.add(accessCondition2); + addAccessCondition.add(new AddOperation("/sections/defaultAC/accessConditions", - accessCondition)); + accessConditions)); String patchBody = getPatchContent(addAccessCondition); getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) @@ -6309,7 +6309,9 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", is("administrator"))) - .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("openaccess"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()); } @Test From d426f449acaf92d8bb5b7d8911549874c519d3b4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 20 Dec 2021 18:18:09 +0100 Subject: [PATCH 0580/1254] fix bug with array access conditions in add patch operation --- .../AccessConditionAddPatchOperation.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java index 228f91fda8..814718c13f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.submit.factory.impl; import java.sql.SQLException; import java.text.ParseException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -48,27 +48,40 @@ public class AccessConditionAddPatchOperation extends AddPatchOperation accessConditions = Arrays.asList(evaluateSingleObject((LateObjectEvaluator) value)); - - verifyAccessConditions(context, configuration, accessConditions); //"path": "/sections/<:name-of-the-form>/accessConditions/-" String[] split = getAbsolutePath(path).split("/"); + List accessConditions = parseAccessConditions(path, value, split); + + verifyAccessConditions(context, configuration, accessConditions); + if (split.length == 1) { // to replace completely the access conditions authorizeService.removePoliciesActionFilter(context, item, Constants.READ); } - - // check duplicate policy - checkDuplication(context, item, accessConditions); + if (split.length == 2) { + // check duplicate policy + checkDuplication(context, item, accessConditions); + } // apply policies AccessConditionResourcePolicyUtils.findApplyResourcePolicy(context, configuration.getOptions(), item, accessConditions); } + private List parseAccessConditions(String path, Object value, String[] split) { + List accessConditions = new ArrayList(); + if (split.length == 1) { + accessConditions = evaluateArrayObject((LateObjectEvaluator) value); + } else if (split.length == 2) { + accessConditions.add(evaluateSingleObject((LateObjectEvaluator) value)); + } else { + throw new UnprocessableEntityException("The patch operation for path:" + path + " is not supported!"); + } + return accessConditions; + } + private void verifyAccessConditions(Context context, AccessConditionConfiguration configuration, List accessConditions) throws SQLException, AuthorizeException, ParseException { for (AccessConditionDTO dto : accessConditions) { From d5caa24b5081dbe0133b8f49cefdb5a1595d7d83 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 20 Dec 2021 12:19:24 -0600 Subject: [PATCH 0581/1254] Rename LICENSE_HEADER to LICENSE.header and update all references --- LICENSE_HEADER => LICENSE.header | 0 dspace/src/main/assembly/assembly.xml | 2 +- pom.xml | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) rename LICENSE_HEADER => LICENSE.header (100%) diff --git a/LICENSE_HEADER b/LICENSE.header similarity index 100% rename from LICENSE_HEADER rename to LICENSE.header diff --git a/dspace/src/main/assembly/assembly.xml b/dspace/src/main/assembly/assembly.xml index 6591d7bb73..97cfac8ed3 100644 --- a/dspace/src/main/assembly/assembly.xml +++ b/dspace/src/main/assembly/assembly.xml @@ -39,7 +39,7 @@ README - LICENSE_HEADER + LICENSE.header diff --git a/pom.xml b/pom.xml index 2980b9436c..d21dda4dd7 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ true + as it is used to reference the LICENSE.header and *.properties file(s) in that directory. --> ${basedir} @@ -400,13 +400,13 @@ true - + com.mycila license-maven-plugin -

    ${root.basedir}/LICENSE_HEADER
    +
    ${root.basedir}/LICENSE.header
    src/** From ce125148bb8786fbb4a5651b5894dca9ba7ede3a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 21 Dec 2021 12:31:10 +0100 Subject: [PATCH 0582/1254] refactored security plugin --- ...geReportRestPermissionEvaluatorPlugin.java | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java index cf91b5c050..f75059f4a1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java @@ -69,32 +69,31 @@ public class UsageReportRestPermissionEvaluatorPlugin extends RestObjectPermissi Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); UUID uuidObject = null; try { + if (Objects.isNull(targetId)) { + return true; + } if (configurationService.getBooleanProperty("usage-statistics.authorization.admin.usage", true)) { return authorizeService.isAdmin(context); - } - if (Objects.nonNull(targetId)) { - if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME, targetType)) { - if (StringUtils.countMatches(targetId.toString(), "_") != 1) { - throw new IllegalArgumentException("Must end in objectUUID_reportId, example: " - + "1911e8a4-6939-490c-b58b-a5d70f8d91fb_TopCountries"); - } - // Get uuid from uuidDSO_reportId pathParam - uuidObject = UUID.fromString(StringUtils.substringBefore(targetId.toString(), "_")); - } else if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME + "search", targetType)) { - // Get uuid from url (selfLink of dso) queryParam - uuidObject = UUID.fromString(StringUtils.substringAfterLast(targetId.toString(), "/")); - } else { - return false; + } else if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME, targetType)) { + if (StringUtils.countMatches(targetId.toString(), "_") != 1) { + throw new IllegalArgumentException("Must end in objectUUID_reportId, example: " + + "1911e8a4-6939-490c-b58b-a5d70f8d91fb_TopCountries"); } - - DSpaceObject dso = dspaceObjectUtil.findDSpaceObject(context, uuidObject); - // If the dso is null then we give permission so we can throw another status code instead - if (Objects.isNull(dso)) { - return true; - } - - return authorizeService.authorizeActionBoolean(context, dso, restPermission.getDspaceApiActionId()); + // Get uuid from uuidDSO_reportId pathParam + uuidObject = UUID.fromString(StringUtils.substringBefore(targetId.toString(), "_")); + } else if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME + "search", targetType)) { + // Get uuid from url (selfLink of dso) queryParam + uuidObject = UUID.fromString(StringUtils.substringAfterLast(targetId.toString(), "/")); + } else { + return false; } + + DSpaceObject dso = dspaceObjectUtil.findDSpaceObject(context, uuidObject); + // If the dso is null then we give permission so we can throw another status code instead + if (Objects.isNull(dso)) { + return true; + } + return authorizeService.authorizeActionBoolean(context, dso, restPermission.getDspaceApiActionId()); } catch (SQLException e) { log.error(e.getMessage(), e); } From 38cce095905c36568fe00afd04d31a1c05b25c61 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 21 Dec 2021 12:32:47 +0100 Subject: [PATCH 0583/1254] added tests with statistics visible to all --- .../app/rest/StatisticsRestRepositoryIT.java | 173 ++++++++++++++---- 1 file changed, 141 insertions(+), 32 deletions(-) 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 73559a9adc..aa8ce3b4cf 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 @@ -379,6 +379,36 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .matchUsageReport(itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) ); + + // only admin access visits report + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isForbidden()); + + getClient().perform( + get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isUnauthorized()); + + // make statistics visible to all + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint))))); + + getClient().perform( + get("/api/statistics/usagereports/" + + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint))))); } @Test @@ -420,6 +450,25 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform( get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) .andExpect(status().isUnauthorized()); + + // make statistics visible to all + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, + Arrays.asList(expectedPoint))))); + + getClient().perform( + get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, + Arrays.asList(expectedPoint))))); } @Test @@ -451,38 +500,25 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform( get("/api/statistics/usagereports/" + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) .andExpect(status().isUnauthorized()); - } - @Test - public void totalVisitsReport_Bitstream_NotVisited_WithUsageStatisticsParameterDisabled() throws Exception { + // make statistics visible to all configurationService.setProperty("usage-statistics.authorization.admin.usage", false); - // ** WHEN ** - // Bitstream is never visited - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 0); - expectedPoint.setType("bitstream"); - expectedPoint.setId(bitstreamNotVisited.getID().toString()); - String authToken = getAuthToken(admin.getEmail(), password); - // And request that bitstream's TotalVisits stat report - getClient(authToken).perform( - get("/api/statistics/usagereports/" + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) - // ** THEN ** - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); - - String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform( - get("/api/statistics/usagereports/" + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) - .andExpect(status().isNotFound()); + get("/api/statistics/usagereports/" + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, + Arrays.asList(expectedPoint))))); - getClient().perform( - get("/api/statistics/usagereports/" + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) - .andExpect(status().isNotFound()); + getClient().perform( + get("/api/statistics/usagereports/" + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, + Arrays.asList(expectedPoint))))); } @Test @@ -520,6 +556,25 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) .andExpect(status().isUnauthorized()); + + // make statistics visible to all + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, expectedPoints)))); + + getClient().perform( + get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, expectedPoints)))); } @Test @@ -612,6 +667,26 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform( get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) .andExpect(status().isUnauthorized()); + + // make statistics visible to all + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + // only admin has access to downloads report + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, TOTAL_DOWNLOADS_REPORT_ID, + Arrays.asList(expectedPoint))))); + + getClient().perform( + get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, TOTAL_DOWNLOADS_REPORT_ID, + Arrays.asList(expectedPoint))))); } @Test @@ -710,6 +785,25 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform( get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) .andExpect(status().isUnauthorized()); + + // make statistics visible to all + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, TOP_COUNTRIES_REPORT_ID, + Arrays.asList(expectedPoint))))); + + getClient().perform( + get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(UsageReportMatcher.matchUsageReport( + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, TOP_COUNTRIES_REPORT_ID, + Arrays.asList(expectedPoint))))); } /** @@ -809,6 +903,25 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient().perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) .andExpect(status().isUnauthorized()); + + // make statistics visible to all + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + + getClient(loggedInToken).perform( + get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is( + UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, Arrays.asList(expectedPoint))))); + + getClient().perform( + get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is( + UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, Arrays.asList(expectedPoint))))); } /** @@ -1072,7 +1185,6 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes public void usageReportsSearch_Collection_NotVisited() throws Exception { // ** WHEN ** // Collection is not visited -try { UsageReportPointDsoTotalVisitsRest expectedPointTotalVisits = new UsageReportPointDsoTotalVisitsRest(); expectedPointTotalVisits.addValue("views", 0); expectedPointTotalVisits.setType("collection"); @@ -1097,9 +1209,6 @@ try { TOP_CITIES_REPORT_ID, new ArrayList<>()), UsageReportMatcher.matchUsageReport(collectionNotVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, TOP_COUNTRIES_REPORT_ID, new ArrayList<>())))); - } finally { - System.out.println(""); - } } @Test @@ -1310,7 +1419,7 @@ try { } else { expectedPoint.addValue("views", viewsLastMonth); } - String month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()); + String month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, new Locale("en")); expectedPoint.setId(month + " " + cal.get(Calendar.YEAR)); expectedPoints.add(expectedPoint); From 76827e46c4597e3b913aec0d62b086c85566a7f9 Mon Sep 17 00:00:00 2001 From: Corrado Lombardi Date: Tue, 21 Dec 2021 15:17:25 +0100 Subject: [PATCH 0584/1254] removed TODO --- dspace/config/dspace.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a2c27ffad9..64ca5b15f2 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -141,7 +141,6 @@ mail.from.address = dspace-noreply@myu.edu # When feedback is submitted via the Feedback form, it is sent to this address # Currently limited to one recipient! -# TODO: UNSUPPORTED in DSpace 7.0 feedback.recipient = dspace-help@myu.edu # General site administration (Webmaster) e-mail From e0ff0d10320fbfb0e7768e2e5be5be3a71fa6e53 Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Wed, 22 Dec 2021 00:31:30 +0000 Subject: [PATCH 0585/1254] Tag GoogleRecorderEventListener as deprecated --- .../java/org/dspace/google/GoogleRecorderEventListener.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java index ec86e5b410..d70ab50dba 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java @@ -40,7 +40,9 @@ import org.springframework.beans.factory.annotation.Autowired; * Time: 10:05 * * Notify Google Analytics of... well anything we want really. + * @deprecated */ +@Deprecated public class GoogleRecorderEventListener extends AbstractUsageEventListener { private String analyticsKey; From 16e7758b826eea8db1a5f51482de235a33f6d552 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 22 Dec 2021 09:05:21 -0600 Subject: [PATCH 0586/1254] Add comment on what to use instead --- .../java/org/dspace/google/GoogleRecorderEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java index d70ab50dba..4159661b1c 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleRecorderEventListener.java @@ -40,7 +40,7 @@ import org.springframework.beans.factory.annotation.Autowired; * Time: 10:05 * * Notify Google Analytics of... well anything we want really. - * @deprecated + * @deprecated Use org.dspace.google.GoogleAsyncEventListener instead */ @Deprecated public class GoogleRecorderEventListener extends AbstractUsageEventListener { From 19371747aa9b2cb9e57d9f4793f2e840e6347e84 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 22 Dec 2021 17:44:54 +0100 Subject: [PATCH 0587/1254] 86163: Feedback on Shibboleth changes --- .../dspace/authenticate/AuthenticationServiceImpl.java | 1 + .../java/org/dspace/authenticate/LDAPAuthentication.java | 8 ++++---- .../org/dspace/authenticate/PasswordAuthentication.java | 4 ++-- .../java/org/dspace/authenticate/ShibAuthentication.java | 6 +++--- .../java/org/dspace/authenticate/X509Authentication.java | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java index 13aa581e2b..1270c1cb2c 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java @@ -194,6 +194,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { return getAuthenticationMethodStack().iterator(); } + @Override public String getAuthenticationMethod(final Context context, final HttpServletRequest request) { final Iterator authenticationMethodIterator = authenticationMethodIterator(); diff --git a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java index 3f9659f0c3..520a5f62a6 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/LDAPAuthentication.java @@ -264,7 +264,7 @@ public class LDAPAuthentication if (ldap.ldapAuthenticate(dn, password, context)) { context.setCurrentUser(eperson); - request.getSession().setAttribute(LDAP_AUTHENTICATED, true); + request.setAttribute(LDAP_AUTHENTICATED, true); // assign user to groups based on ldap dn assignGroups(dn, ldap.ldapGroup, context); @@ -315,7 +315,7 @@ public class LDAPAuthentication context.dispatchEvents(); context.restoreAuthSystemState(); context.setCurrentUser(eperson); - request.getSession().setAttribute(LDAP_AUTHENTICATED, true); + request.setAttribute(LDAP_AUTHENTICATED, true); // assign user to groups based on ldap dn @@ -347,7 +347,7 @@ public class LDAPAuthentication ePersonService.update(context, eperson); context.dispatchEvents(); context.setCurrentUser(eperson); - request.getSession().setAttribute(LDAP_AUTHENTICATED, true); + request.setAttribute(LDAP_AUTHENTICATED, true); // assign user to groups based on ldap dn @@ -747,7 +747,7 @@ public class LDAPAuthentication public boolean isUsed(final Context context, final HttpServletRequest request) { if (request != null && context.getCurrentUser() != null && - request.getSession().getAttribute(LDAP_AUTHENTICATED) != null) { + request.getAttribute(LDAP_AUTHENTICATED) != null) { return true; } return false; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java index c7687d1a9e..50a685872a 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java @@ -220,7 +220,7 @@ public class PasswordAuthentication // login is ok if password matches: context.setCurrentUser(eperson); if (request != null) { - request.getSession().setAttribute(PASSWORD_AUTHENTICATED, true); + request.setAttribute(PASSWORD_AUTHENTICATED, true); } log.info(LogHelper.getHeader(context, "authenticate", "type=PasswordAuthentication")); return SUCCESS; @@ -259,7 +259,7 @@ public class PasswordAuthentication public boolean isUsed(final Context context, final HttpServletRequest request) { if (request != null && context.getCurrentUser() != null && - request.getSession().getAttribute(PASSWORD_AUTHENTICATED) != null) { + request.getAttribute(PASSWORD_AUTHENTICATED) != null) { return true; } return false; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java index e2947e1610..a913d27d62 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java @@ -235,7 +235,7 @@ public class ShibAuthentication implements AuthenticationMethod { // Step 4: Log the user in. context.setCurrentUser(eperson); - request.getSession().setAttribute("shib.authenticated", true); + request.setAttribute("shib.authenticated", true); AuthenticateServiceFactory.getInstance().getAuthenticationService().initEPerson(context, request, eperson); log.info(eperson.getEmail() + " has been authenticated via shibboleth."); @@ -403,7 +403,7 @@ public class ShibAuthentication implements AuthenticationMethod { // Cache the special groups, so we don't have to recalculate them again // for this session. - request.getSession().setAttribute("shib.specialgroup", groupIds); + request.setAttribute("shib.specialgroup", groupIds); return new ArrayList<>(groups); } catch (Throwable t) { @@ -1287,7 +1287,7 @@ public class ShibAuthentication implements AuthenticationMethod { public boolean isUsed(final Context context, final HttpServletRequest request) { if (request != null && context.getCurrentUser() != null && - request.getSession().getAttribute("shib.authenticated") != null) { + request.getAttribute("shib.authenticated") != null) { return true; } return false; diff --git a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java index 39e246f3cc..6bd7435743 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java @@ -546,7 +546,7 @@ public class X509Authentication implements AuthenticationMethod { context.dispatchEvents(); context.restoreAuthSystemState(); context.setCurrentUser(eperson); - request.getSession().setAttribute(X509_AUTHENTICATED, true); + request.setAttribute(X509_AUTHENTICATED, true); setSpecialGroupsFlag(request, email); return SUCCESS; } else { @@ -603,7 +603,7 @@ public class X509Authentication implements AuthenticationMethod { public boolean isUsed(final Context context, final HttpServletRequest request) { if (request != null && context.getCurrentUser() != null && - request.getSession().getAttribute(X509_AUTHENTICATED) != null) { + request.getAttribute(X509_AUTHENTICATED) != null) { return true; } return false; From 8c1dcb207299f8c7b2e2ff808d0440e48930cf47 Mon Sep 17 00:00:00 2001 From: jose vicente ribelles aguilar Date: Thu, 23 Dec 2021 13:53:30 +0100 Subject: [PATCH 0588/1254] Update V7.0_2019.07.31__Retrieval_of_name_variant.sql fix oracle slq sintax --- .../oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql index 4d61de791a..cebae09f65 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019.07.31__Retrieval_of_name_variant.sql @@ -14,5 +14,5 @@ ALTER TABLE relationship ADD leftward_value VARCHAR2(50); ALTER TABLE relationship ADD rightward_value VARCHAR2(50); -ALTER TABLE relationship_type RENAME left_label TO leftward_type; -ALTER TABLE relationship_type RENAME right_label TO rightward_type; +ALTER TABLE relationship_type RENAME COLUMN left_label TO leftward_type; +ALTER TABLE relationship_type RENAME COLUMN right_label TO rightward_type; From 18515b1c9a1944560983567442bce8922c568c5d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 23 Dec 2021 11:44:02 -0600 Subject: [PATCH 0589/1254] Update docker-compose scripts to use env variables (align with runtime configs for UI) --- docker-compose-cli.yml | 13 ++++++++++-- docker-compose.yml | 21 +++++++++++++++++-- dspace/src/main/docker-compose/README.md | 2 -- .../docker-compose/docker-compose-angular.yml | 18 +++++++++------- .../main/docker-compose/environment.dev.ts | 18 ---------------- 5 files changed, 40 insertions(+), 32 deletions(-) delete mode 100644 dspace/src/main/docker-compose/environment.dev.ts diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml index 1a53f36189..0489f7f6d1 100644 --- a/docker-compose-cli.yml +++ b/docker-compose-cli.yml @@ -7,9 +7,18 @@ services: build: context: . dockerfile: Dockerfile.cli - #environment: + environment: + # Below syntax may look odd, but it is how to override dspace.cfg settings via env variables. + # See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml + # __P__ => "." (e.g. dspace__P__dir => dspace.dir) + # __D__ => "-" (e.g. google__D__metadata => google-metadata) + # dspace.dir + dspace__P__dir: /dspace + # db.url: Ensure we are using the 'dspacedb' image for our database + db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace' + # solr.server: Ensure we are using the 'dspacesolr' image for Solr + solr__P__server: http://dspacesolr:8983/solr volumes: - - ./dspace/src/main/docker-compose/local.cfg:/dspace/config/local.cfg - assetstore:/dspace/assetstore entrypoint: /dspace/bin/dspace command: help diff --git a/docker-compose.yml b/docker-compose.yml index 9e2583ecad..6b61456ca2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,12 +4,29 @@ networks: ipam: config: # Define a custom subnet for our DSpace network, so that we can easily trust requests from host to container. - # If you customize this value, be sure to customize the 'proxies.trusted.ipranges' in your local.cfg. + # If you customize this value, be sure to customize the 'proxies.trusted.ipranges' env variable below. - subnet: 172.23.0.0/16 services: # DSpace (backend) webapp container dspace: container_name: dspace + environment: + # Below syntax may look odd, but it is how to override dspace.cfg settings via env variables. + # See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml + # __P__ => "." (e.g. dspace__P__dir => dspace.dir) + # __D__ => "-" (e.g. google__D__metadata => google-metadata) + # dspace.dir, dspace.server.url, dspace.ui.url and dspace.name + dspace__P__dir: /dspace + dspace__P__server__P__url: http://localhost:8080/server + dspace__P__ui__P__url: http://localhost:4000 + dspace__P__name: 'DSpace Started with Docker Compose' + # db.url: Ensure we are using the 'dspacedb' image for our database + db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace' + # solr.server: Ensure we are using the 'dspacesolr' image for Solr + solr__P__server: http://dspacesolr:8983/solr + # proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests + # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above. + proxies__P__trusted__P__ipranges: '172.23.0' image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" build: context: . @@ -26,8 +43,8 @@ services: stdin_open: true tty: true volumes: + # Keep DSpace assetstore directory between reboots - assetstore:/dspace/assetstore - - ./dspace/src/main/docker-compose/local.cfg:/dspace/config/local.cfg # Ensure that the database is ready BEFORE starting tomcat # 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep # 2. Then, run database migration to init database tables diff --git a/dspace/src/main/docker-compose/README.md b/dspace/src/main/docker-compose/README.md index 97d26bd8ee..91b292ad8a 100644 --- a/dspace/src/main/docker-compose/README.md +++ b/dspace/src/main/docker-compose/README.md @@ -25,8 +25,6 @@ - docker-compose-shibboleth.yml - Docker compose file that will start a *test/demo* Shibboleth SP container (in Apache) that proxies requests to the DSpace container - ONLY useful for testing/development. NOT production ready. -- environment.dev.ts - - Default angular environment when testing DSpace-angular from this repo ## To refresh / pull DSpace images from Dockerhub ``` diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index 7eb78c5c07..00cdeb4bbb 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -15,13 +15,17 @@ services: depends_on: - dspace environment: - DSPACE_HOST: dspace-angular - DSPACE_NAMESPACE: / - DSPACE_PORT: '4000' - DSPACE_SSL: "false" - image: dspace/dspace-angular:latest + DSPACE_UI_SSL: false + DSPACE_UI_HOST: dspace-angular + DSPACE_UI_PORT: '4000' + DSPACE_UI_NAMESPACE: / + DSPACE_REST_SSL: false + DSPACE_REST_HOST: localhost + DSPACE_REST_PORT: 8080 + DSPACE_REST_NAMESPACE: /server + image: dspace/dspace-angular:dspace-7_x networks: - dspacenet: {} + dspacenet: ports: - published: 4000 target: 4000 @@ -29,5 +33,3 @@ services: target: 9876 stdin_open: true tty: true - volumes: - - ./dspace/src/main/docker-compose/environment.dev.ts:/app/src/environments/environment.dev.ts diff --git a/dspace/src/main/docker-compose/environment.dev.ts b/dspace/src/main/docker-compose/environment.dev.ts deleted file mode 100644 index 0e603ef11d..0000000000 --- a/dspace/src/main/docker-compose/environment.dev.ts +++ /dev/null @@ -1,18 +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/ - */ -// This file is based on environment.template.ts provided by Angular UI -export const environment = { - // Default to using the local REST API (running in Docker) - rest: { - ssl: false, - host: 'localhost', - port: 8080, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/server' - } -}; From ae4cbe6278a1033e0bc05297fd53e136dfca0dd5 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 23 Dec 2021 19:21:31 +0100 Subject: [PATCH 0590/1254] [CST-4505] Add access condition step definition to item-submission.xml --- dspace/config/item-submission.xml | 7 +++++++ dspace/config/spring/api/access-conditions.xml | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index e6597d68da..3c13c88b46 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -149,6 +149,13 @@ cclicense + + + submit.progressbar.accessCondition + org.dspace.app.rest.submit.step.AccessConditionStep + accessCondition + + 9.4.41.v20210516 - 2.17.0 + 2.17.1 2.0.24 3.17 1.7.25 From e3dbf3832a3bafbecadebc6bcb08ee169b3b3635 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Tue, 4 Jan 2022 11:36:25 +0100 Subject: [PATCH 0598/1254] XmlWorkflowServiceIT adding a "restoreAuthSystemState()" to reinstate the authentication system prior to performing the tests --- .../test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java | 1 + 1 file changed, 1 insertion(+) 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 fce1e9ebac..69c4dc16f4 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java @@ -69,6 +69,7 @@ public class XmlWorkflowServiceIT extends AbstractIntegrationTestWithDatabase { Workflow workflow = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory().getWorkflow(colWithWorkflow); ClaimedTask taskToReject = ClaimedTaskBuilder.createClaimedTask(context, colWithWorkflow, submitter) .withTitle("Test workflow item to reject").build(); + context.restoreAuthSystemState(); // Submitter person is both original submitter as well as reviewer, should have edit access of claimed task assertTrue(this.containsRPForUser(taskToReject.getWorkflowItem().getItem(), submitter, Constants.WRITE)); From 7618a1e0db632385ed1364f3f8c9acbb8a607353 Mon Sep 17 00:00:00 2001 From: Corrado Lombardi Date: Tue, 4 Jan 2022 18:19:56 +0100 Subject: [PATCH 0599/1254] [CST-4981] default statistics visibility to users with READ permissions, to preserve current default behavior --- dspace/config/modules/usage-statistics.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/modules/usage-statistics.cfg b/dspace/config/modules/usage-statistics.cfg index 834fd0ba1a..c77bb1ca78 100644 --- a/dspace/config/modules/usage-statistics.cfg +++ b/dspace/config/modules/usage-statistics.cfg @@ -24,7 +24,7 @@ usage-statistics.resolver.timeout = 200 # If disabled, anyone with READ permissions on the DSpaceObject will be able # to view the statistics. #View/download statistics -usage-statistics.authorization.admin.usage=true +usage-statistics.authorization.admin.usage=false #Search/search result statistics usage-statistics.authorization.admin.search=true #Workflow result statistics From ee7b1df18742e3330e2147a71b7947eadfe396f5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 4 Jan 2022 12:29:05 -0600 Subject: [PATCH 0600/1254] Increase test's max duration per call by 0.1 ms --- .../java/org/dspace/content/MetadataFieldPerformanceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataFieldPerformanceTest.java b/dspace-api/src/test/java/org/dspace/content/MetadataFieldPerformanceTest.java index 3ba7f0556b..fefddbf3d9 100644 --- a/dspace-api/src/test/java/org/dspace/content/MetadataFieldPerformanceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/MetadataFieldPerformanceTest.java @@ -74,7 +74,7 @@ public class MetadataFieldPerformanceTest extends AbstractUnitTest { long duration = (endTime - startTime); - double maxDurationPerCall = .3; + double maxDurationPerCall = .4; double maxDuration = maxDurationPerCall * amount; //Duration is 1.542 without performance improvements //Duration is 0.0538 with performance improvements From c214e6c041d45add0b2b5e709efc2e3a9764df28 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 4 Jan 2022 12:31:05 -0600 Subject: [PATCH 0601/1254] Log adding of a new metadatavalue (we already log updates & deletes, but not adds) --- .../main/java/org/dspace/content/MetadataValueServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java index 78bfd40b28..0c34c04f30 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValueServiceImpl.java @@ -55,6 +55,8 @@ public class MetadataValueServiceImpl implements MetadataValueService { //An update here isn't needed, this is persited upon the merge of the owning object // metadataValueDAO.save(context, metadataValue); metadataValue = metadataValueDAO.create(context, metadataValue); + log.info(LogHelper.getHeader(context, "add_metadatavalue", + "metadata_value_id=" + metadataValue.getID())); return metadataValue; } From 99fe85b608e4f19daac0c09bab47ee92b4cfec21 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 4 Jan 2022 12:30:06 -0600 Subject: [PATCH 0602/1254] Remove noisy/duplicative debug logging about identifiers (clutters our test environment logs) --- .../dspace/content/DSpaceObjectServiceImpl.java | 14 -------------- .../dspace/identifier/IdentifierServiceImpl.java | 3 --- 2 files changed, 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index d9020e6141..6b188396f6 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -95,20 +95,6 @@ public abstract class DSpaceObjectServiceImpl implements } } - if (log.isDebugEnabled()) { - StringBuilder dbgMsg = new StringBuilder(); - for (String id : identifiers) { - if (dbgMsg.capacity() == 0) { - dbgMsg.append("This DSO's Identifiers are: "); - } else { - dbgMsg.append(", "); - } - dbgMsg.append(id); - } - dbgMsg.append("."); - log.debug(dbgMsg.toString()); - } - return identifiers; } 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 b3375d66dc..cd28242a4b 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -150,7 +150,6 @@ public class IdentifierServiceImpl implements IdentifierService { + "Identifier for " + contentServiceFactory.getDSpaceObjectService(dso) .getTypeText(dso) + ", " + dso.getID().toString() + "."); - log.debug(ex.getMessage(), ex); } catch (IdentifierException e) { log.error(e.getMessage(), e); } @@ -181,7 +180,6 @@ public class IdentifierServiceImpl implements IdentifierService { + "Identifier for " + contentServiceFactory.getDSpaceObjectService(dso) .getTypeText(dso) + ", " + dso.getID().toString() + "."); - log.debug(ex.getMessage(), ex); } catch (IdentifierException ex) { log.error(ex.getMessage(), ex); } @@ -228,7 +226,6 @@ public class IdentifierServiceImpl implements IdentifierService { log.info(service.getClass().getName() + " cannot resolve " + "Identifier " + identifier + ": identifier not " + "found."); - log.debug(ex.getMessage(), ex); } catch (IdentifierException ex) { log.error(ex.getMessage(), ex); } From 102f285f6afad8f3e471e031fd069563e328bcf6 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 4 Jan 2022 13:11:29 -0600 Subject: [PATCH 0603/1254] Fix overactive logging when lookup(context, dso) method is called --- .../org/dspace/identifier/IdentifierServiceImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 cd28242a4b..d0b6e4417e 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -151,7 +151,7 @@ public class IdentifierServiceImpl implements IdentifierService { .getTypeText(dso) + ", " + dso.getID().toString() + "."); } catch (IdentifierException e) { - log.error(e.getMessage(), e); + log.error(e); } } } @@ -161,6 +161,8 @@ public class IdentifierServiceImpl implements IdentifierService { @Override public List lookup(Context context, DSpaceObject dso) { List identifiers = new ArrayList<>(); + // Attempt to lookup DSO's identifiers using every available provider + // TODO: We may want to eventually limit providers based on DSO type, as not every provider supports every DSO for (IdentifierProvider service : providers) { try { String result = service.lookup(context, dso); @@ -176,12 +178,14 @@ public class IdentifierServiceImpl implements IdentifierService { identifiers.add(result); } } catch (IdentifierNotFoundException ex) { - log.info(service.getClass().getName() + " doesn't find an " + // This IdentifierNotFoundException is NOT logged by default, as some providers do not apply to + // every DSO (e.g. DOIs usually don't apply to EPerson objects). So it is expected some may fail lookup. + log.debug(service.getClass().getName() + " doesn't find an " + "Identifier for " + contentServiceFactory.getDSpaceObjectService(dso) .getTypeText(dso) + ", " + dso.getID().toString() + "."); } catch (IdentifierException ex) { - log.error(ex.getMessage(), ex); + log.error(ex); } } From 24355b7a1a5e368a6adf09184ac781c408218867 Mon Sep 17 00:00:00 2001 From: Corrado Lombardi Date: Wed, 5 Jan 2022 15:14:54 +0100 Subject: [PATCH 0604/1254] [CST-4981] statistics usage restricted to admin via configuration --- .../org/dspace/app/rest/StatisticsRestRepositoryIT.java | 8 ++++++++ .../rest/authorization/ViewUsageStatisticsFeatureIT.java | 8 ++++++++ 2 files changed, 16 insertions(+) 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 aa8ce3b4cf..292a9a53d6 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 @@ -57,6 +57,7 @@ import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.statistics.factory.StatisticsServiceFactory; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -102,6 +103,7 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // Explicitly use solr commit in SolrLoggerServiceImpl#postView configurationService.setProperty("solr-statistics.autoCommit", false); + configurationService.setProperty("usage-statistics.authorization.admin.usage", true); context.turnOffAuthorisationSystem(); @@ -124,6 +126,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); } + @After + public void tearDown() { + + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + } + @Test public void usagereports_withoutId_NotImplementedException() throws Exception { getClient().perform(get("/api/statistics/usagereports")) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java index 146389fc01..8d767bd5c8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java @@ -44,6 +44,7 @@ import org.dspace.content.Item; import org.dspace.content.Site; import org.dspace.content.service.SiteService; import org.dspace.services.ConfigurationService; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -127,6 +128,13 @@ public class ViewUsageStatisticsFeatureIT extends AbstractControllerIntegrationT collectionARest = collectionConverter.convert(collectionA, Projection.DEFAULT); itemARest = itemConverter.convert(itemA, Projection.DEFAULT); bitstreamARest = bitstreamConverter.convert(bitstreamA, Projection.DEFAULT); + configurationService.setProperty("usage-statistics.authorization.admin.usage", true); + } + + @After + public void tearDown() { + + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); } @Test From 7b4287aa6d9ac9992e9e4396ca2d3a1898c41b9d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 5 Jan 2022 12:12:57 -0600 Subject: [PATCH 0605/1254] Expose submitter versioning setting for #1385 --- dspace/config/modules/rest.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 2355c3c1b6..1b0895810b 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -29,8 +29,10 @@ rest.projection.specificLevel.maxEmbed = 5 # rest endpoint. If a rest request is made for a property which exists, but isn't listed here, the server will # respond that the property wasn't found. This property can be defined multiple times to allow access to multiple # configuration properties. +# (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 = versioning.item.history.include.submitter #---------------------------------------------------------------# # These configs are used by the deprecated REST (v4-6) module # From 9b1b4c1f76f76f0d3b2844f9608098bee1456559 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 5 Jan 2022 12:21:13 -0600 Subject: [PATCH 0606/1254] Correct description based on UI implementation --- dspace/config/modules/versioning.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace/config/modules/versioning.cfg b/dspace/config/modules/versioning.cfg index c69d63e57c..a345294242 100644 --- a/dspace/config/modules/versioning.cfg +++ b/dspace/config/modules/versioning.cfg @@ -12,9 +12,9 @@ # If disabled anyone with READ permissions on the item will be able to view the versioning history versioning.item.history.view.admin=false -# The property item.history.include.submitter controls whether the name of -# the submitter of a version should be included in the version history of -# an item. +# Controls whether the name of the submitter of a version should be included in the version history of an item. +# When true, the submitter name will be shown only if you have administrative privileges on the Item. +# When false, the submitter name is never shown. versioning.item.history.include.submitter=false # If you want to allow submitters to create new versions of there items, set From 316864bb53b2aef85656ab05c5741b5414498982 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 5 Jan 2022 14:41:14 -0600 Subject: [PATCH 0607/1254] Switch to %X{var} syntax instead of $${ctx:var} --- dspace/config/log4j2.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace/config/log4j2.xml b/dspace/config/log4j2.xml index eeab2bc9c6..3bca2187c9 100644 --- a/dspace/config/log4j2.xml +++ b/dspace/config/log4j2.xml @@ -28,8 +28,10 @@ fileName='${log.dir}/dspace.log' > + + pattern='%d %-5p %equals{%X{correlationID}}{}{unknown} %equals{%X{requestID}}{}{unknown} %c @ %m%n'/> yyyy-MM-dd + + From dc31ae3358466160aac7201f4222469918e4ad57 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 18 Jan 2022 09:27:07 +0100 Subject: [PATCH 0632/1254] Implement community feedbacks --- .../UsageReportRestPermissionEvaluatorPlugin.java | 2 +- .../app/rest/StatisticsRestRepositoryIT.java | 15 +++++++-------- .../ViewUsageStatisticsFeatureIT.java | 7 ------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java index f75059f4a1..06254d561a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/UsageReportRestPermissionEvaluatorPlugin.java @@ -72,7 +72,7 @@ public class UsageReportRestPermissionEvaluatorPlugin extends RestObjectPermissi if (Objects.isNull(targetId)) { return true; } - if (configurationService.getBooleanProperty("usage-statistics.authorization.admin.usage", true)) { + if (configurationService.getBooleanProperty("usage-statistics.authorization.admin.usage", false)) { return authorizeService.isAdmin(context); } else if (StringUtils.equalsIgnoreCase(UsageReportRest.NAME, targetType)) { if (StringUtils.countMatches(targetId.toString(), "_") != 1) { 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 292a9a53d6..fb46316c8e 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 @@ -57,7 +57,6 @@ import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.statistics.factory.StatisticsServiceFactory; import org.hamcrest.Matchers; -import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -126,12 +125,6 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); } - @After - public void tearDown() { - - configurationService.setProperty("usage-statistics.authorization.admin.usage", false); - } - @Test public void usagereports_withoutId_NotImplementedException() throws Exception { getClient().perform(get("/api/statistics/usagereports")) @@ -157,6 +150,13 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().is(HttpStatus.NOT_FOUND.value())); } + @Test + public void usagereports_nonValidReportIDpart_Exception_By_Anonymous_Test() throws Exception { + getClient().perform(get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + + "_NotValidReport")) + .andExpect(status().is(HttpStatus.NOT_FOUND.value())); + } + @Test public void usagereports_NonExistentUUID_Exception() throws Exception { getClient(adminToken).perform( @@ -679,7 +679,6 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // make statistics visible to all configurationService.setProperty("usage-statistics.authorization.admin.usage", false); - // only admin has access to downloads report getClient(loggedInToken).perform( get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) .andExpect(status().isOk()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java index 8d767bd5c8..188e21ec1f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ViewUsageStatisticsFeatureIT.java @@ -44,7 +44,6 @@ import org.dspace.content.Item; import org.dspace.content.Site; import org.dspace.content.service.SiteService; import org.dspace.services.ConfigurationService; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -131,12 +130,6 @@ public class ViewUsageStatisticsFeatureIT extends AbstractControllerIntegrationT configurationService.setProperty("usage-statistics.authorization.admin.usage", true); } - @After - public void tearDown() { - - configurationService.setProperty("usage-statistics.authorization.admin.usage", false); - } - @Test public void adminBitstreamTestNotFound() throws Exception { String adminToken = getAuthToken(admin.getEmail(), password); From 70484065343b71eaeee75de5e8b04006926a36ef Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 18 Jan 2022 11:05:40 +0100 Subject: [PATCH 0633/1254] fix failed tests --- .../org/dspace/authorize/ResourcePolicy.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java index 97fa68ca6c..a25a492a3a 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java @@ -24,6 +24,7 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import org.apache.solr.common.StringUtils; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; @@ -120,7 +121,22 @@ public class ResourcePolicy implements ReloadableEntity { return false; } final ResourcePolicy other = (ResourcePolicy) obj; - if (!Objects.equals(getID(), other.getID())) { + if (!StringUtils.equals(getRpName(), other.getRpName())) { + return false; + } + if (getAction() != other.getAction()) { + return false; + } + if (!Objects.equals(getEPerson(), other.getEPerson())) { + return false; + } + if (!Objects.equals(getGroup(), other.getGroup())) { + return false; + } + if (!Objects.equals(getStartDate(), other.getStartDate())) { + return false; + } + if (!Objects.equals(getEndDate(), other.getEndDate())) { return false; } return true; From e380a98a33a80adc7546c5279d3a5af12683e32b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 18 Jan 2022 12:53:21 +0100 Subject: [PATCH 0634/1254] fix test and add new one --- .../app/rest/StatisticsRestRepositoryIT.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) 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 fb46316c8e..5dd24451c5 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 @@ -151,10 +151,18 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes } @Test - public void usagereports_nonValidReportIDpart_Exception_By_Anonymous_Test() throws Exception { + public void usagereports_nonValidReportIDpart_Exception_By_Anonymous_Unauthorized_Test() throws Exception { getClient().perform(get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_NotValidReport")) - .andExpect(status().is(HttpStatus.NOT_FOUND.value())); + .andExpect(status().isUnauthorized()); + } + + @Test + public void usagereports_nonValidReportIDpart_Exception_By_Anonymous_Test() throws Exception { + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + getClient().perform(get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + + "_NotValidReport")) + .andExpect(status().isNotFound()); } @Test @@ -225,6 +233,39 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isForbidden()); } + @Test + public void usagereport_loggedInUserReadRights_and_usage_statistics_admin_is_false_Test() throws Exception { + configurationService.setProperty("usage-statistics.authorization.admin.usage", false); + context.turnOffAuthorisationSystem(); + authorizeService.removeAllPolicies(context, itemNotVisitedWithBitstreams); + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(itemNotVisitedWithBitstreams) + .withAction(Constants.READ) + .withUser(eperson).build(); + + EPerson eperson1 = EPersonBuilder.createEPerson(context) + .withEmail("eperson1@mail.com") + .withPassword(password) + .build(); + context.restoreAuthSystemState(); + String anotherLoggedInUserToken = getAuthToken(eperson1.getEmail(), password); + // We request a dso's TotalVisits usage stat report as anon but dso has no read policy for anon + getClient().perform(get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isUnauthorized()); + + // We request a dso's TotalVisits usage stat report as logged in eperson and has read policy for this user + getClient(loggedInToken).perform(get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isOk()); + + // We request a dso's TotalVisits usage stat report as another logged + // in eperson and has no read policy for this user + getClient(anotherLoggedInUserToken).perform( + get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID)) + .andExpect(status().isForbidden()); + } + @Test public void totalVisitsReport_Community_Visited() throws Exception { // ** WHEN ** From 8f7fc0d5ca00eaf73b565f79190371eaac8795ae Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 18 Jan 2022 13:54:59 +0100 Subject: [PATCH 0635/1254] 85542: Add more logging for rest process call --- .../app/mediafilter/MediaFilterScript.java | 1 + .../mediafilter/MediaFilterServiceImpl.java | 61 ++++++++++++++----- .../service/MediaFilterService.java | 7 +++ 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java index 36d3a1fe87..5fbbebbb28 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java @@ -122,6 +122,7 @@ public class MediaFilterScript extends DSpaceRunnable> filterFormats) { this.filterFormats = filterFormats; } + + @Override + public void setLogHandler(DSpaceRunnableHandler handler) { + this.handler = handler; + } } diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java index 83198a5083..50a6bb3a20 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java @@ -16,6 +16,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.core.Context; +import org.dspace.scripts.handler.DSpaceRunnableHandler; /** * MediaFilterManager is the class that invokes the media/format filters over the @@ -124,4 +125,10 @@ public interface MediaFilterService { public void setSkipList(List skipList); public void setFilterFormats(Map> filterFormats); + + /** + * Set the log handler used in the DSpace scripts and processes framework + * @param handler + */ + public void setLogHandler(DSpaceRunnableHandler handler); } From 0c8ee5da3c70d7dbdedc070a515db3a86d924325 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 18 Jan 2022 14:55:28 +0100 Subject: [PATCH 0636/1254] 85542: Tweak to error logging --- .../app/mediafilter/MediaFilterServiceImpl.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 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 2a26a6b62e..50efa68ff4 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 @@ -228,14 +228,15 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB int assetstore = myBitstream.getStoreNumber(); // Printout helpful information to find the errored bitstream. - logError("ERROR filtering, skipping bitstream:\n"); - logError("\tItem Handle: " + handle); + StringBuilder sb = new StringBuilder("ERROR filtering, skipping bitstream:\n"); + sb.append("\tItem Handle: ").append(handle); for (Bundle bundle : bundles) { - logError("\tBundle Name: " + bundle.getName()); + sb.append("\tBundle Name: ").append(bundle.getName()); } - logError("\tFile Size: " + size); - logError("\tChecksum: " + checksum); - logError("\tAsset Store: " + assetstore); + sb.append("\tFile Size: ").append(size); + sb.append("\tChecksum: ").append(checksum); + sb.append("\tAsset Store: ").append(assetstore); + logError(sb.toString()); logError(e.getMessage(), e); } } else if (filterClass instanceof SelfRegisterInputFormats) { From 618db16b318e6c1c69e88fdcb53ffa6015b29201 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 18 Jan 2022 16:59:37 -0800 Subject: [PATCH 0637/1254] Multiple updates. --- .../canvasdimension/CanvasDimensionCLI.java | 64 +++++++++++-- .../IIIFApiQueryServiceImpl.java | 14 +-- .../IIIFCanvasDimensionServiceImpl.java | 6 +- .../canvasdimension/ImageDimensionReader.java | 4 +- .../{app => iiif}/canvasdimension/Util.java | 2 +- .../IIIFCanvasDimensionServiceFactory.java | 4 +- ...IIIFCanvasDimensionServiceFactoryImpl.java | 4 +- .../service/IIIFApiQueryService.java | 2 +- .../service/IIIFCanvasDimensionService.java | 2 +- .../canvasdimension/CanvasDimensionsIT.java | 92 ++++++++++++++----- dspace/config/launcher.xml | 6 +- dspace/config/spring/api/iiif-processing.xml | 6 +- 12 files changed, 146 insertions(+), 60 deletions(-) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/CanvasDimensionCLI.java (71%) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/IIIFApiQueryServiceImpl.java (88%) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/IIIFCanvasDimensionServiceImpl.java (97%) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/ImageDimensionReader.java (92%) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/Util.java (95%) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java (87%) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java (85%) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/service/IIIFApiQueryService.java (91%) rename dspace-api/src/main/java/org/dspace/{app => iiif}/canvasdimension/service/IIIFCanvasDimensionService.java (97%) diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/CanvasDimensionCLI.java similarity index 71% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/CanvasDimensionCLI.java index 1ea5c2f26f..f827ae7ee9 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/CanvasDimensionCLI.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension; +package org.dspace.iiif.canvasdimension; import java.util.Arrays; import java.util.UUID; @@ -17,18 +17,19 @@ import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.MissingArgumentException; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import org.dspace.app.canvasdimension.factory.IIIFCanvasDimensionServiceFactory; -import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; 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; import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.iiif.canvasdimension.factory.IIIFCanvasDimensionServiceFactory; +import org.dspace.iiif.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -61,7 +62,9 @@ public class CanvasDimensionCLI { int max2Process = Integer.MAX_VALUE; String identifier = null; + String typeString = null; String eperson = null; + int dsoType = -1; Context context = new Context(); IIIFCanvasDimensionService canvasProcessor = IIIFCanvasDimensionServiceFactory.getInstance() @@ -72,14 +75,18 @@ public class CanvasDimensionCLI { Options options = new Options(); options.addOption("i", "identifier", true, "process IIIF canvas dimensions for images belonging to this identifier"); + options.addOption("t", "type", true, + "type: COMMUNITY, COLLECTION or ITEM\""); options.addOption("e", "eperson", true, "email of eperson setting the canvas dimensions"); options.addOption("f", "force", false, "force update of all IIIF canvas height and width dimensions"); options.addOption("q", "quiet", false, - "do not print anything except in the event of errors."); + "do not print anything except in the event of errors"); options.addOption("m", "maximum", true, "process no more than maximum items"); + options.addOption("h", "help", false, + "display help"); Option skipOption = Option.builder("s") .longOpt("skip") @@ -104,6 +111,17 @@ public class CanvasDimensionCLI { System.exit(1); } + if (line.hasOption('h')) { + HelpFormatter help = new HelpFormatter(); + help.printHelp("CanvasDimension processor\n", options); + System.out + .println("\nUUID example: iiif-canvas-dimensions -e user@email.org " + + "-i 1086306d-8a51-43c3-98b9-c3b00f49105f -t COLLECTION"); + System.out + .println("\nHandle example: iiif-canvas-dimensions -e user@email.org -i 123456/21"); + System.exit(0); + } + if (line.hasOption('f')) { force = true; } @@ -116,9 +134,29 @@ public class CanvasDimensionCLI { if (line.hasOption('i')) { identifier = line.getOptionValue('i'); } else { + HelpFormatter help = new HelpFormatter(); + help.printHelp("CanvasDimension processor\n", options); System.out.println("An identifier for a Community, Collection, or Item must be provided."); System.exit(1); } + if (line.hasOption('t')) { + typeString = line.getOptionValue('t'); + if ("ITEM".equalsIgnoreCase(typeString)) { + dsoType = Constants.ITEM; + } else if ("COLLECTION".equals(typeString)) { + dsoType = Constants.COLLECTION; + } else if ("COMMUNITY".equalsIgnoreCase(typeString)) { + dsoType = Constants.COMMUNITY; + } + } else { + // If the identifier is a handle dsoType is not required. + if (identifier.indexOf('/') == -1) { + HelpFormatter help = new HelpFormatter(); + help.printHelp("CanvasDimension processor\n", options); + System.out.println("A DSpace type must be provided: COMMUNITY, COLLECTION or ITEM."); + System.exit(1); + } + } if (line.hasOption('m')) { max2Process = Integer.parseInt(line.getOptionValue('m')); if (max2Process <= 1) { @@ -144,11 +182,20 @@ public class CanvasDimensionCLI { canvasProcessor.setSkipList(Arrays.asList(skipIds)); } + DSpaceObject dso = null; + if (identifier.indexOf('/') != -1) { + dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, identifier); + } else if (dsoType == Constants.COMMUNITY) { + dso = ContentServiceFactory.getInstance().getCommunityService().find(context, UUID.fromString(identifier)); + } else if (dsoType == Constants.COLLECTION) { + dso = ContentServiceFactory.getInstance().getCollectionService().find(context, UUID.fromString(identifier)); + } else if (dsoType == Constants.ITEM) { + dso = ContentServiceFactory.getInstance().getItemService().find(context, UUID.fromString(identifier)); + } - DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, identifier); if (dso == null) { throw new IllegalArgumentException("Cannot resolve " - + identifier + " to a DSpace object"); + + identifier + " to a DSpace object using type: " + typeString); } EPerson user; @@ -189,10 +236,13 @@ public class CanvasDimensionCLI { processed = 1; break; default: + System.out.println("Unsupported object type."); break; } // commit changes - context.commit(); + if (processed >= 1) { + context.commit(); + } // Always print summary to standard out. System.out.println(processed + " IIIF items were processed."); diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFApiQueryServiceImpl.java similarity index 88% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFApiQueryServiceImpl.java index 3eed9b83ab..dabf119326 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFApiQueryServiceImpl.java @@ -5,9 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension; +package org.dspace.iiif.canvasdimension; -import static org.dspace.app.canvasdimension.Util.checkDimensions; +import static org.dspace.iiif.canvasdimension.Util.checkDimensions; import java.io.BufferedReader; import java.io.IOException; @@ -18,10 +18,9 @@ import java.net.URL; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.Logger; -import org.dspace.app.canvasdimension.service.IIIFApiQueryService; import org.dspace.content.Bitstream; +import org.dspace.iiif.canvasdimension.service.IIIFApiQueryService; import org.dspace.iiif.util.IIIFSharedUtils; -import org.springframework.beans.factory.InitializingBean; /** @@ -30,15 +29,10 @@ import org.springframework.beans.factory.InitializingBean; * * @author Michael Spalti mspalti@willamette.edu */ -public class IIIFApiQueryServiceImpl implements IIIFApiQueryService, InitializingBean { +public class IIIFApiQueryServiceImpl implements IIIFApiQueryService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFApiQueryServiceImpl.class); - @Override - public void afterPropertiesSet() throws Exception { - // do nothing - } - @Override public int[] getImageDimensions(Bitstream bitstream) { return getIiifImageDimensions(bitstream); diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java similarity index 97% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java index 8708f04f0d..99e0a6c6e1 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/IIIFCanvasDimensionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension; +package org.dspace.iiif.canvasdimension; import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT; import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE; @@ -19,8 +19,6 @@ import java.util.Iterator; import java.util.List; import java.util.Optional; -import org.dspace.app.canvasdimension.service.IIIFApiQueryService; -import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -33,6 +31,8 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.iiif.canvasdimension.service.IIIFApiQueryService; +import org.dspace.iiif.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.iiif.util.IIIFSharedUtils; import org.springframework.beans.factory.annotation.Autowired; diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/ImageDimensionReader.java similarity index 92% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/ImageDimensionReader.java index 648d98528c..4e46c8b43f 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/ImageDimensionReader.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/ImageDimensionReader.java @@ -5,9 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension; +package org.dspace.iiif.canvasdimension; -import static org.dspace.app.canvasdimension.Util.checkDimensions; +import static org.dspace.iiif.canvasdimension.Util.checkDimensions; import java.awt.image.BufferedImage; import java.io.IOException; diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/Util.java similarity index 95% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/Util.java index 3245ade1ca..630febfcbf 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/Util.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/Util.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension; +package org.dspace.iiif.canvasdimension; /** * Utilities for IIIF canvas dimension processing. diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java similarity index 87% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java index 75dad46c80..56e5cfee95 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/factory/IIIFCanvasDimensionServiceFactory.java @@ -5,9 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension.factory; +package org.dspace.iiif.canvasdimension.factory; -import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; +import org.dspace.iiif.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.services.factory.DSpaceServicesFactory; /** diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java similarity index 85% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java index abe44f540d..0ab17a29a4 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/factory/IIIFCanvasDimensionServiceFactoryImpl.java @@ -5,9 +5,9 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension.factory; +package org.dspace.iiif.canvasdimension.factory; -import org.dspace.app.canvasdimension.service.IIIFCanvasDimensionService; +import org.dspace.iiif.canvasdimension.service.IIIFCanvasDimensionService; import org.springframework.beans.factory.annotation.Autowired; /** diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFApiQueryService.java similarity index 91% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFApiQueryService.java index 86e9b91623..e2041fe7b8 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFApiQueryService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFApiQueryService.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension.service; +package org.dspace.iiif.canvasdimension.service; import org.dspace.content.Bitstream; diff --git a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFCanvasDimensionService.java similarity index 97% rename from dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java rename to dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFCanvasDimensionService.java index ba3840b20b..7e49e4ada7 100644 --- a/dspace-api/src/main/java/org/dspace/app/canvasdimension/service/IIIFCanvasDimensionService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFCanvasDimensionService.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension.service; +package org.dspace.iiif.canvasdimension.service; import java.util.List; diff --git a/dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java b/dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java index 7d5bc48e91..b44dca9d79 100644 --- a/dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java +++ b/dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java @@ -97,7 +97,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String handle = iiifItem.getHandle(); - execCanvasScript(handle); + execCanvasScriptHandle(handle); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -127,8 +127,8 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { .build(); context.restoreAuthSystemState(); - String handle = col1.getHandle(); - execCanvasScript(handle); + String id = col1.getID().toString(); + execCanvasScript(id, "COLLECTION"); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -158,8 +158,9 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { .build(); context.restoreAuthSystemState(); - String handle = child1.getHandle(); - execCanvasScript(handle); + String id = child1.getID().toString(); + execCanvasScript(id, "COMMUNITY"); + // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -190,7 +191,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String handle = parentCommunity.getHandle(); - execCanvasScript(handle); + execCanvasScriptHandle(handle); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -233,8 +234,9 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); - String handle = parentCommunity.getHandle(); - execCanvasScript(handle); + String id = parentCommunity.getID().toString(); + execCanvasScript(id, "COMMUNITY"); + // All bitstreams should be updated with canvas metadata. assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -272,8 +274,43 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { .build(); context.restoreAuthSystemState(); - String handle = iiifItem.getHandle(); - execCanvasScriptForceOption(handle); + String id = iiifItem.getID().toString(); + execCanvasScriptForceOption(id, "ITEM"); + + // The existing metadata should be updated + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) + .anyMatch(m -> m.getValue().contentEquals("400"))); + assertTrue(bitstream.getMetadata().stream() + .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_WIDTH)) + .anyMatch(m -> m.getValue().contentEquals("600"))); + + } + + @Test + public void processCollectionWithForce() throws Exception { + context.turnOffAuthorisationSystem(); + // Create a new Item + iiifItem = ItemBuilder.createItem(context, col1) + .withTitle("Test Item") + .withIssueDate("2017-10-17") + .enableIIIF() + .build(); + + // Add jpeg image bitstream (300 x 200) + InputStream input = this.getClass().getResourceAsStream("cat.jpg"); + bitstream = BitstreamBuilder + .createBitstream(context, iiifItem, input) + .withName("Bitstream2.jpg") + .withMimeType("image/jpeg") + .withIIIFCanvasWidth(100) + .withIIIFCanvasHeight(100) + .build(); + context.restoreAuthSystemState(); + + String id = col1.getID().toString(); + execCanvasScriptForceOption(id, "COLLECTION"); + // The existing metadata should be updated assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -306,7 +343,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String handle = iiifItem.getHandle(); - execCanvasScript(handle); + execCanvasScriptHandle(handle); // The existing canvas metadata should be unchanged assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -367,9 +404,9 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); - String handle = parentCommunity.getHandle(); + String id = parentCommunity.getID().toString(); - execCanvasScriptWithMaxRecs(handle); + execCanvasScriptWithMaxRecs(id, "COMMUNITY"); // check System.out for number of items processed. assertEquals("2 IIIF items were processed.\n", outContent.toString()); } @@ -426,9 +463,9 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); - String handle = parentCommunity.getHandle(); + String id = parentCommunity.getID().toString(); - execCanvasScriptWithSkipList(handle, col2.getHandle() + "," + col3.getHandle()); + execCanvasScriptWithSkipList(id, "COMMUNITY", col2.getHandle() + "," + col3.getHandle()); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -489,9 +526,9 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); - String handle = parentCommunity.getHandle(); + String id = parentCommunity.getID().toString(); - execCanvasScriptWithSkipList(handle, col2.getHandle()); + execCanvasScriptWithSkipList(id, "COMMUNITY", col2.getHandle()); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -509,21 +546,26 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { } - private void execCanvasScript(String handle) throws Exception { - runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle); + private void execCanvasScriptHandle(String handle) throws Exception { + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", handle); } - private void execCanvasScriptForceOption(String handle) throws Exception { - runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-f"); + private void execCanvasScript(String id, String type) throws Exception { + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-t", type); } - private void execCanvasScriptWithMaxRecs(String handle) throws Exception { + private void execCanvasScriptForceOption(String id, String type) throws Exception { + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-f", "-t", type); + } + + private void execCanvasScriptWithMaxRecs(String id, String type) throws Exception { // maximum 2 - runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-m", "2", "-f", "-q"); + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", + id, "-t", type, "-m", "2", "-f", "-q"); } - private void execCanvasScriptWithSkipList(String handle, String skip) throws Exception { - runDSpaceScript("canvas-dimensions", "-e", "admin@email.com", "-i", handle, "-s", skip, "-f"); + private void execCanvasScriptWithSkipList(String id, String type, String skip) throws Exception { + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-t", type, "-s", skip, "-f"); } } diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index 7e26e6a3af..c62d9c7980 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -367,10 +367,10 @@ - canvas-dimensions - Add canvas width and height metadata for IIIF enabled items. + iiif-canvas-dimensions + Automatically add the IIIF canvas width/height metadata to Bitstreams of IIIF enabled Items. - org.dspace.app.canvasdimension.CanvasDimensionCLI + org.dspace.iiif.canvasdimension.CanvasDimensionCLI diff --git a/dspace/config/spring/api/iiif-processing.xml b/dspace/config/spring/api/iiif-processing.xml index bfe06de26e..17fbcaa836 100644 --- a/dspace/config/spring/api/iiif-processing.xml +++ b/dspace/config/spring/api/iiif-processing.xml @@ -3,8 +3,8 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true"> - - - + + + From 5bbe4430bf18cba287a48f49502b0da14adb2058 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 19 Jan 2022 10:26:49 +0100 Subject: [PATCH 0638/1254] removed flush method --- dspace-api/src/main/java/org/dspace/core/Context.java | 9 --------- .../src/main/java/org/dspace/core/DBConnection.java | 7 ------- .../main/java/org/dspace/core/HibernateDBConnection.java | 5 ----- .../impl/AccessConditionReplacePatchOperation.java | 1 - dspace/config/item-submission.xml | 4 ++-- 5 files changed, 2 insertions(+), 24 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 1c65fbcea0..f5edcf2eb0 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -897,15 +897,6 @@ public class Context implements AutoCloseable { currentUser = reloadEntity(currentUser); } - /** - * Force this session to flush. - * - * @throws SQLException passed through. - */ - public void flush() throws SQLException { - dbConnection.flush(); - } - public String getAuthenticationMethod() { return authenticationMethod; } diff --git a/dspace-api/src/main/java/org/dspace/core/DBConnection.java b/dspace-api/src/main/java/org/dspace/core/DBConnection.java index ebbb412153..cb5825eec1 100644 --- a/dspace-api/src/main/java/org/dspace/core/DBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/DBConnection.java @@ -59,13 +59,6 @@ public interface DBConnection { */ public void rollback() throws SQLException; - /** - * Force this session to flush. - * - * @throws SQLException passed through. - */ - public void flush() throws SQLException; - /** * Close this session: close DBMS connection(s) and clean up resources. * diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index 4fdb295880..3321e4d837 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -114,11 +114,6 @@ public class HibernateDBConnection implements DBConnection { return sessionFactory.getCurrentSession() != null && sessionFactory.getCurrentSession().isOpen(); } - @Override - public void flush() throws SQLException { - getSession().flush(); - } - /** * Rollback any changes applied to the current Transaction. This also closes the Transaction. A new Transaction * may be opened the next time getSession() is called. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java index f31b799fd6..4014a6a595 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java @@ -78,7 +78,6 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< if (checkDuplication(context, policies, accessConditionDTO, idxToReplace, item)) { item.getResourcePolicies().remove(policies.get(idxToReplace)); resourcePolicyService.delete(context, policies.get(idxToReplace)); - context.flush(); AccessConditionOption option = getOption(configuration, accessConditionDTO); option.createResourcePolicy(context, item, accessConditionDTO.getName(), null, accessConditionDTO.getStartDate(),accessConditionDTO.getEndDate()); diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 5e3939a3f8..702af8f865 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -232,8 +232,8 @@ - - + + From 441c62239a61ce3197b0aab986175d94801ba068 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Wed, 19 Jan 2022 13:23:02 +0100 Subject: [PATCH 0639/1254] Fix inputstream connection leak when downloading files: Bugfix for cover pages so that the correct file size is used --- .../app/rest/BitstreamRestController.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 7185dccfdb..d22e5c8131 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -134,10 +134,17 @@ public class BitstreamRestController { } try { + long filesize; + if (citationDocumentService.isCitationEnabledForBitstream(bit, context)) { + filesize = citationDocumentService.getCitedDocumentLength(context, bit); + } else { + filesize = bit.getSizeBytes(); + } + HttpHeadersInitializer httpHeadersInitializer = new HttpHeadersInitializer() .withBufferSize(BUFFER_SIZE) .withFileName(name) - .withLength(bit.getSizeBytes()) + .withLength(filesize) .withChecksum(bit.getChecksum()) .withMimetype(mimetype) .with(request) @@ -149,16 +156,10 @@ public class BitstreamRestController { //Determine if we need to send the file as a download or if the browser can open it inline long dispositionThreshold = configurationService.getLongProperty("webui.content_disposition_threshold"); - if (dispositionThreshold >= 0 && bit.getSizeBytes() > dispositionThreshold) { + if (dispositionThreshold >= 0 && filesize > dispositionThreshold) { httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT); } - long filesize; - if (citationDocumentService.isCitationEnabledForBitstream(bit, context)) { - filesize = citationDocumentService.getCitedDocumentLength(context, bit); - } else { - filesize = bit.getSizeBytes(); - } org.dspace.app.rest.utils.BitstreamResource bitstreamResource = From f6a0fcb64be44c9bf5e6c3d1c72255f1860a7046 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Tue, 18 Jan 2022 10:25:46 +0100 Subject: [PATCH 0640/1254] [Ticket: 918] Email issues fix --- dspace-api/pom.xml | 4 ++-- dspace-services/pom.xml | 4 ++-- pom.xml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 03685d53bd..a0885aa57f 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -481,8 +481,8 @@ commons-validator - javax.mail - mail + com.sun.mail + javax.mail javax.servlet diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 17ca993bef..e37e0d370d 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -104,8 +104,8 @@ provided - javax.mail - mail + com.sun.mail + javax.mail org.mockito diff --git a/pom.xml b/pom.xml index 13509777e9..1d989d2c89 100644 --- a/pom.xml +++ b/pom.xml @@ -1400,9 +1400,9 @@ 2.9.2 - javax.mail - mail - 1.4.7 + com.sun.mail + javax.mail + 1.6.2 javax.servlet From 5396216aa8ee488f465038e29312d3344b6bdb86 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Thu, 20 Jan 2022 14:09:29 +0100 Subject: [PATCH 0641/1254] Logging exception when sending emails fails in the workflow --- .../java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 d77eb16ea7..8900222907 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -681,7 +681,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { } } catch (MessagingException e) { log.warn(LogManager.getHeader(context, "notifyOfArchive", - "cannot email user" + " item_id=" + item.getID())); + "cannot email user" + " item_id=" + item.getID()), e); } } @@ -715,7 +715,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { } catch (MessagingException e) { log.warn(LogManager.getHeader(c, "notifyOfCuration", "cannot email users of workflow_item_id " + wi.getID() - + ": " + e.getMessage())); + + ": " + e.getMessage()), e); } } @@ -1195,7 +1195,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { log.warn(LogManager.getHeader(c, "notify_of_reject", "cannot email user" + " eperson_id" + e.getID() + " eperson_email" + e.getEmail() - + " workflow_item_id" + wi.getID())); + + " workflow_item_id" + wi.getID()), ex); } } From a15478178dbc4e7a5d9362ba27974a12a392e22e Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Thu, 20 Jan 2022 16:56:06 +0100 Subject: [PATCH 0642/1254] Removing duplicate Null check --- .../main/java/org/dspace/app/rest/BitstreamRestController.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index d22e5c8131..ab227682b4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -107,9 +107,6 @@ public class BitstreamRestController { Context context = ContextUtil.obtainContext(request); Bitstream bit = bitstreamService.find(context, uuid); - if (bit == null) { - throw new ResourceNotFoundException(); - } EPerson currentUser = context.getCurrentUser(); if (bit == null) { From 17ba8f5621faf3baa65f83df05b176cc95231339 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 20 Jan 2022 14:13:11 -0600 Subject: [PATCH 0643/1254] Environment variables must be strings or numbers, not booleans --- dspace/src/main/docker-compose/docker-compose-angular.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index 00cdeb4bbb..00dde2e831 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -15,11 +15,11 @@ services: depends_on: - dspace environment: - DSPACE_UI_SSL: false + DSPACE_UI_SSL: 'false' DSPACE_UI_HOST: dspace-angular DSPACE_UI_PORT: '4000' DSPACE_UI_NAMESPACE: / - DSPACE_REST_SSL: false + DSPACE_REST_SSL: 'false' DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: /server From 53cb1505f47976eda0bf9426c5424c7da6ae3772 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 20 Jan 2022 17:50:24 -0800 Subject: [PATCH 0644/1254] Modified CLI and renamed packages. --- .../service => }/IIIFApiQueryService.java | 2 +- .../IIIFApiQueryServiceImpl.java | 3 +- .../canvasdimension/CanvasDimensionCLI.java | 40 ++++------------ .../IIIFCanvasDimensionServiceImpl.java | 2 +- .../{ => consumer}/CacheEvictBeanLocator.java | 2 +- .../{ => consumer}/CacheEvictService.java | 2 +- .../IIIFCacheEventConsumer.java | 2 +- .../canvasdimension/CanvasDimensionsIT.java | 45 ++++++++---------- .../{app => iiif}/canvasdimension/cat.jpg | Bin dspace/config/dspace.cfg | 2 +- dspace/config/spring/api/iiif-processing.xml | 2 +- 11 files changed, 36 insertions(+), 66 deletions(-) rename dspace-api/src/main/java/org/dspace/iiif/{canvasdimension/service => }/IIIFApiQueryService.java (91%) rename dspace-api/src/main/java/org/dspace/iiif/{canvasdimension => }/IIIFApiQueryServiceImpl.java (96%) rename dspace-api/src/main/java/org/dspace/iiif/{ => consumer}/CacheEvictBeanLocator.java (97%) rename dspace-api/src/main/java/org/dspace/iiif/{ => consumer}/CacheEvictService.java (96%) rename dspace-api/src/main/java/org/dspace/iiif/{ => consumer}/IIIFCacheEventConsumer.java (99%) rename dspace-api/src/test/java/org/dspace/{app => iiif}/canvasdimension/CanvasDimensionsIT.java (95%) rename dspace-api/src/test/resources/org/dspace/{app => iiif}/canvasdimension/cat.jpg (100%) diff --git a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFApiQueryService.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryService.java similarity index 91% rename from dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFApiQueryService.java rename to dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryService.java index e2041fe7b8..4f4543ac78 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/service/IIIFApiQueryService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryService.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.iiif.canvasdimension.service; +package org.dspace.iiif; import org.dspace.content.Bitstream; diff --git a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java similarity index 96% rename from dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFApiQueryServiceImpl.java rename to dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java index dabf119326..5d3a78b35d 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.iiif.canvasdimension; +package org.dspace.iiif; import static org.dspace.iiif.canvasdimension.Util.checkDimensions; @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.Logger; import org.dspace.content.Bitstream; -import org.dspace.iiif.canvasdimension.service.IIIFApiQueryService; import org.dspace.iiif.util.IIIFSharedUtils; diff --git a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/CanvasDimensionCLI.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/CanvasDimensionCLI.java index f827ae7ee9..50b934d110 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/CanvasDimensionCLI.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/CanvasDimensionCLI.java @@ -17,11 +17,11 @@ import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.MissingArgumentException; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.dspace.app.util.factory.UtilServiceFactory; 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; import org.dspace.eperson.EPerson; @@ -62,9 +62,7 @@ public class CanvasDimensionCLI { int max2Process = Integer.MAX_VALUE; String identifier = null; - String typeString = null; String eperson = null; - int dsoType = -1; Context context = new Context(); IIIFCanvasDimensionService canvasProcessor = IIIFCanvasDimensionServiceFactory.getInstance() @@ -75,8 +73,6 @@ public class CanvasDimensionCLI { Options options = new Options(); options.addOption("i", "identifier", true, "process IIIF canvas dimensions for images belonging to this identifier"); - options.addOption("t", "type", true, - "type: COMMUNITY, COLLECTION or ITEM\""); options.addOption("e", "eperson", true, "email of eperson setting the canvas dimensions"); options.addOption("f", "force", false, @@ -116,9 +112,10 @@ public class CanvasDimensionCLI { help.printHelp("CanvasDimension processor\n", options); System.out .println("\nUUID example: iiif-canvas-dimensions -e user@email.org " + - "-i 1086306d-8a51-43c3-98b9-c3b00f49105f -t COLLECTION"); + "-i 1086306d-8a51-43c3-98b9-c3b00f49105f"); System.out - .println("\nHandle example: iiif-canvas-dimensions -e user@email.org -i 123456/21"); + .println("\nHandle example: iiif-canvas-dimensions -e user@email.org " + + "-i 123456789/12"); System.exit(0); } @@ -139,24 +136,6 @@ public class CanvasDimensionCLI { System.out.println("An identifier for a Community, Collection, or Item must be provided."); System.exit(1); } - if (line.hasOption('t')) { - typeString = line.getOptionValue('t'); - if ("ITEM".equalsIgnoreCase(typeString)) { - dsoType = Constants.ITEM; - } else if ("COLLECTION".equals(typeString)) { - dsoType = Constants.COLLECTION; - } else if ("COMMUNITY".equalsIgnoreCase(typeString)) { - dsoType = Constants.COMMUNITY; - } - } else { - // If the identifier is a handle dsoType is not required. - if (identifier.indexOf('/') == -1) { - HelpFormatter help = new HelpFormatter(); - help.printHelp("CanvasDimension processor\n", options); - System.out.println("A DSpace type must be provided: COMMUNITY, COLLECTION or ITEM."); - System.exit(1); - } - } if (line.hasOption('m')) { max2Process = Integer.parseInt(line.getOptionValue('m')); if (max2Process <= 1) { @@ -185,17 +164,14 @@ public class CanvasDimensionCLI { DSpaceObject dso = null; if (identifier.indexOf('/') != -1) { dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, identifier); - } else if (dsoType == Constants.COMMUNITY) { - dso = ContentServiceFactory.getInstance().getCommunityService().find(context, UUID.fromString(identifier)); - } else if (dsoType == Constants.COLLECTION) { - dso = ContentServiceFactory.getInstance().getCollectionService().find(context, UUID.fromString(identifier)); - } else if (dsoType == Constants.ITEM) { - dso = ContentServiceFactory.getInstance().getItemService().find(context, UUID.fromString(identifier)); + } else { + dso = UtilServiceFactory.getInstance().getDSpaceObjectUtils() + .findDSpaceObject(context, UUID.fromString(identifier)); } if (dso == null) { throw new IllegalArgumentException("Cannot resolve " - + identifier + " to a DSpace object using type: " + typeString); + + identifier + " to a DSpace object."); } EPerson user; diff --git a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java index 99e0a6c6e1..d985786a04 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java @@ -31,7 +31,7 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.iiif.canvasdimension.service.IIIFApiQueryService; +import org.dspace.iiif.IIIFApiQueryService; import org.dspace.iiif.canvasdimension.service.IIIFCanvasDimensionService; import org.dspace.iiif.util.IIIFSharedUtils; import org.springframework.beans.factory.annotation.Autowired; diff --git a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictBeanLocator.java similarity index 97% rename from dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java rename to dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictBeanLocator.java index b82242e6e1..0e55e99d16 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictBeanLocator.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictBeanLocator.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.iiif; +package org.dspace.iiif.consumer; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; diff --git a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java similarity index 96% rename from dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java rename to dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java index e1b9676039..fa4a61ac79 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/CacheEvictService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.iiif; +package org.dspace.iiif.consumer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java similarity index 99% rename from dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java rename to dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java index 3d39570fc6..9aa8b64c73 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.iiif; +package org.dspace.iiif.consumer; import java.util.HashSet; import java.util.Set; diff --git a/dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java b/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java similarity index 95% rename from dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java rename to dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java index b44dca9d79..502266da06 100644 --- a/dspace-api/src/test/java/org/dspace/app/canvasdimension/CanvasDimensionsIT.java +++ b/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.app.canvasdimension; +package org.dspace.iiif.canvasdimension; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -97,7 +97,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String handle = iiifItem.getHandle(); - execCanvasScriptHandle(handle); + execCanvasScript(handle); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -128,7 +128,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String id = col1.getID().toString(); - execCanvasScript(id, "COLLECTION"); + execCanvasScript(id); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -159,7 +159,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String id = child1.getID().toString(); - execCanvasScript(id, "COMMUNITY"); + execCanvasScript(id); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() @@ -191,7 +191,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String handle = parentCommunity.getHandle(); - execCanvasScriptHandle(handle); + execCanvasScript(handle); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -235,7 +235,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String id = parentCommunity.getID().toString(); - execCanvasScript(id, "COMMUNITY"); + execCanvasScript(id); // All bitstreams should be updated with canvas metadata. assertTrue(bitstream.getMetadata().stream() @@ -275,7 +275,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String id = iiifItem.getID().toString(); - execCanvasScriptForceOption(id, "ITEM"); + execCanvasScriptForceOption(id); // The existing metadata should be updated assertTrue(bitstream.getMetadata().stream() @@ -309,7 +309,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String id = col1.getID().toString(); - execCanvasScriptForceOption(id, "COLLECTION"); + execCanvasScriptForceOption(id); // The existing metadata should be updated assertTrue(bitstream.getMetadata().stream() @@ -343,7 +343,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { context.restoreAuthSystemState(); String handle = iiifItem.getHandle(); - execCanvasScriptHandle(handle); + execCanvasScript(handle); // The existing canvas metadata should be unchanged assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -406,7 +406,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { String id = parentCommunity.getID().toString(); - execCanvasScriptWithMaxRecs(id, "COMMUNITY"); + execCanvasScriptWithMaxRecs(id); // check System.out for number of items processed. assertEquals("2 IIIF items were processed.\n", outContent.toString()); } @@ -465,7 +465,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { String id = parentCommunity.getID().toString(); - execCanvasScriptWithSkipList(id, "COMMUNITY", col2.getHandle() + "," + col3.getHandle()); + execCanvasScriptWithSkipList(id,col2.getHandle() + "," + col3.getHandle()); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -528,7 +528,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { String id = parentCommunity.getID().toString(); - execCanvasScriptWithSkipList(id, "COMMUNITY", col2.getHandle()); + execCanvasScriptWithSkipList(id, col2.getHandle()); // The test image is small so the canvas dimension should be doubled, e.g. height 200 -> height 400 assertTrue(bitstream.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_HEIGHT)) @@ -546,26 +546,21 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { } - private void execCanvasScriptHandle(String handle) throws Exception { - runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", handle); + private void execCanvasScript(String id) throws Exception { + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id); } - private void execCanvasScript(String id, String type) throws Exception { - runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-t", type); + private void execCanvasScriptForceOption(String id) throws Exception { + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-f"); } - private void execCanvasScriptForceOption(String id, String type) throws Exception { - runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-f", "-t", type); - } - - private void execCanvasScriptWithMaxRecs(String id, String type) throws Exception { + private void execCanvasScriptWithMaxRecs(String id) throws Exception { // maximum 2 - runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", - id, "-t", type, "-m", "2", "-f", "-q"); + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-m", "2", "-f", "-q"); } - private void execCanvasScriptWithSkipList(String id, String type, String skip) throws Exception { - runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-t", type, "-s", skip, "-f"); + private void execCanvasScriptWithSkipList(String id, String skip) throws Exception { + runDSpaceScript("iiif-canvas-dimensions", "-e", "admin@email.com", "-i", id, "-s", skip, "-f"); } } diff --git a/dspace-api/src/test/resources/org/dspace/app/canvasdimension/cat.jpg b/dspace-api/src/test/resources/org/dspace/iiif/canvasdimension/cat.jpg similarity index 100% rename from dspace-api/src/test/resources/org/dspace/app/canvasdimension/cat.jpg rename to dspace-api/src/test/resources/org/dspace/iiif/canvasdimension/cat.jpg diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a2c27ffad9..8f67d2186a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -733,7 +733,7 @@ event.consumer.authority.class = org.dspace.authority.indexer.AuthorityConsumer event.consumer.authority.filters = Item+Modify|Modify_Metadata # iiif consumer -event.consumer.iiif.class = org.dspace.iiif.IIIFCacheEventConsumer +event.consumer.iiif.class = org.dspace.iiif.consumer.IIIFCacheEventConsumer event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+Remove:Bundle+ALL:Bitstream+All # ...set to true to enable testConsumer messages to standard output diff --git a/dspace/config/spring/api/iiif-processing.xml b/dspace/config/spring/api/iiif-processing.xml index 17fbcaa836..21716516dd 100644 --- a/dspace/config/spring/api/iiif-processing.xml +++ b/dspace/config/spring/api/iiif-processing.xml @@ -5,6 +5,6 @@ - + From 6521564d71ed446f3644ca8ee0a467e273a66071 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 13 Oct 2021 16:31:56 +0200 Subject: [PATCH 0645/1254] [DSC-196] Porting of OpenID authentication provider --- .../authenticate/OidcAuthentication.java | 77 ++++ .../authenticate/OidcAuthenticationBean.java | 260 ++++++++++++++ .../authenticate/OrcidAuthenticationBean.java | 310 +++++++++++++++++ .../dspace/authenticate/oidc/OidcClient.java | 42 +++ .../oidc/OidcClientException.java | 34 ++ .../oidc/impl/OidcClientImpl.java | 148 ++++++++ .../oidc/model/OidcTokenResponseDTO.java | 115 ++++++ .../dspace/app/rest/OidcRestController.java | 58 ++++ .../security/OidcAuthenticationFilter.java | 50 +++ .../security/WebSecurityConfiguration.java | 8 + .../OidcAuthenticationRestControllerIT.java | 328 ++++++++++++++++++ dspace/config/modules/authentication-oidc.cfg | 42 +++ dspace/config/modules/authentication.cfg | 13 + dspace/config/spring/api/core-services.xml | 23 ++ 14 files changed, 1508 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java create mode 100644 dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java create mode 100644 dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java create mode 100644 dspace-api/src/main/java/org/dspace/authenticate/oidc/OidcClient.java create mode 100644 dspace-api/src/main/java/org/dspace/authenticate/oidc/OidcClientException.java create mode 100644 dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/authenticate/oidc/model/OidcTokenResponseDTO.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/OidcAuthenticationRestControllerIT.java create mode 100644 dspace/config/modules/authentication-oidc.cfg diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java new file mode 100644 index 0000000000..354d69c4df --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java @@ -0,0 +1,77 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.authenticate; + +import java.sql.SQLException; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.kernel.ServiceManager; +import org.dspace.utils.DSpace; + +/** + * Implementation of {@link AuthenticationMethod} that delegate all the method + * invocations to the bean of class {@link OrcidAuthenticationBean}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OidcAuthentication implements AuthenticationMethod { + + private final ServiceManager serviceManager = new DSpace().getServiceManager(); + + @Override + public boolean canSelfRegister(Context context, HttpServletRequest request, String username) throws SQLException { + return getOidcAuthentication().canSelfRegister(context, request, username); + } + + @Override + public void initEPerson(Context context, HttpServletRequest request, EPerson eperson) throws SQLException { + getOidcAuthentication().initEPerson(context, request, eperson); + } + + @Override + public boolean allowSetPassword(Context context, HttpServletRequest request, String username) throws SQLException { + return getOidcAuthentication().allowSetPassword(context, request, username); + } + + @Override + public boolean isImplicit() { + return getOidcAuthentication().isImplicit(); + } + + @Override + public List getSpecialGroups(Context context, HttpServletRequest request) throws SQLException { + return getOidcAuthentication().getSpecialGroups(context, request); + } + + @Override + public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request) + throws SQLException { + return getOidcAuthentication().authenticate(context, username, password, realm, request); + } + + @Override + public String loginPageURL(Context context, HttpServletRequest request, HttpServletResponse response) { + return getOidcAuthentication().loginPageURL(context, request, response); + } + + @Override + public String getName() { + return getOidcAuthentication().getName(); + } + + private OidcAuthenticationBean getOidcAuthentication() { + return serviceManager.getServiceByName("oidcAuthentication", OidcAuthenticationBean.class); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java new file mode 100644 index 0000000000..229457f839 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java @@ -0,0 +1,260 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.authenticate; + + +import static java.lang.String.format; +import static java.net.URLEncoder.encode; +import static org.apache.commons.lang.BooleanUtils.toBoolean; +import static org.apache.commons.lang3.StringUtils.isAnyBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; + +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.authenticate.oidc.OidcClient; +import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.EPersonService; +import org.dspace.services.ConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * OpenID Connect Authentication for DSpace. + * + * This implementation doesn't allow/needs to register user, which may be holder + * by the openID authentication server. + * + * @link https://openid.net/developers/specs/ + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + */ +public class OidcAuthenticationBean implements AuthenticationMethod { + + public static final String OIDC_AUTH_ATTRIBUTE = "oidc"; + + private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s"; + + private static final Logger LOGGER = LoggerFactory.getLogger(OidcAuthenticationBean.class); + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private OidcClient oidcClient; + + @Autowired + private EPersonService ePersonService; + + @Override + public boolean allowSetPassword(Context context, HttpServletRequest request, String username) throws SQLException { + return false; + } + + @Override + public boolean isImplicit() { + return false; + } + + @Override + public boolean canSelfRegister(Context context, HttpServletRequest request, String username) throws SQLException { + return canSelfRegister(); + } + + @Override + public void initEPerson(Context context, HttpServletRequest request, EPerson eperson) throws SQLException { + } + + @Override + public List getSpecialGroups(Context context, HttpServletRequest request) throws SQLException { + return List.of(); + } + + @Override + public String getName() { + return OIDC_AUTH_ATTRIBUTE; + } + + @Override + public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request) + throws SQLException { + + if (request == null) { + LOGGER.warn("Unable to authenticate using OIDC because the request object is null."); + return BAD_ARGS; + } + + if (request.getAttribute(OIDC_AUTH_ATTRIBUTE) == null) { + return NO_SUCH_USER; + } + + String code = (String) request.getParameter("code"); + if (StringUtils.isEmpty(code)) { + LOGGER.warn("The incoming request has not code parameter"); + return NO_SUCH_USER; + } + + return authenticateWithOidc(context, code, request); + } + + private int authenticateWithOidc(Context context, String code, HttpServletRequest request) throws SQLException { + + OidcTokenResponseDTO accessToken = getOidcAccessToken(code); + if (accessToken == null) { + LOGGER.warn("No access token retrieved by code"); + return NO_SUCH_USER; + } + + Map userInfo = getOidcUserInfo(accessToken.getAccessToken()); + + String email = getAttributeAsString(userInfo, getEmailAttribute()); + if (StringUtils.isBlank(email)) { + LOGGER.warn("No email found in the user info attributes"); + return NO_SUCH_USER; + } + + EPerson ePerson = ePersonService.findByEmail(context, email); + if (ePerson != null) { + return ePerson.canLogIn() ? logInEPerson(context, ePerson) : BAD_ARGS; + } + + return canSelfRegister() ? registerNewEPerson(context, userInfo, email) : NO_SUCH_USER; + } + + @Override + public String loginPageURL(Context context, HttpServletRequest request, HttpServletResponse response) { + + String authorizeUrl = configurationService.getProperty("authentication-oidc.authorize-endpoint"); + String clientId = configurationService.getProperty("authentication-oidc.client-id"); + String clientSecret = configurationService.getProperty("authentication-oidc.client-secret"); + String redirectUri = configurationService.getProperty("authentication-oidc.redirect-url"); + String tokenUrl = configurationService.getProperty("authentication-oidc.token-endpoint"); + String userInfoUrl = configurationService.getProperty("authentication-oidc.user-info-endpoint"); + String scopes = String.join(" ", configurationService.getArrayProperty("authentication-oidc.scopes")); + String email = getEmailAttribute(); + + if (isAnyBlank(authorizeUrl, clientId, redirectUri, scopes, clientSecret, tokenUrl, userInfoUrl, email)) { + LOGGER.error("Missing mandatory configuration properties for OidcAuthenticationBean"); + return ""; + } + + try { + return format(LOGIN_PAGE_URL_FORMAT, authorizeUrl, clientId, scopes, encode(redirectUri, "UTF-8")); + } catch (UnsupportedEncodingException e) { + LOGGER.error(e.getMessage(), e); + return ""; + } + + } + + private int logInEPerson(Context context, EPerson ePerson) { + context.setCurrentUser(ePerson); + return SUCCESS; + } + + private int registerNewEPerson(Context context, Map userInfo, String email) throws SQLException { + try { + + context.turnOffAuthorisationSystem(); + + EPerson eperson = ePersonService.create(context); + + eperson.setNetid(email); + eperson.setEmail(email); + + String firstName = getAttributeAsString(userInfo, getFirstNameAttribute()); + if (firstName != null) { + eperson.setFirstName(context, firstName); + } + + String lastName = getAttributeAsString(userInfo, getLastNameAttribute()); + if (lastName != null) { + eperson.setLastName(context, lastName); + } + + eperson.setCanLogIn(true); + eperson.setSelfRegistered(true); + + ePersonService.update(context, eperson); + context.setCurrentUser(eperson); + context.dispatchEvents(); + + return SUCCESS; + + } catch (Exception ex) { + LOGGER.error("An error occurs registering a new EPerson from OIDC", ex); + context.rollback(); + return NO_SUCH_USER; + } finally { + context.restoreAuthSystemState(); + } + } + + private OidcTokenResponseDTO getOidcAccessToken(String code) { + try { + return oidcClient.getAccessToken(code); + } catch (Exception ex) { + LOGGER.error("An error occurs retriving the OIDC access_token", ex); + return null; + } + } + + private Map getOidcUserInfo(String accessToken) { + try { + return oidcClient.getUserInfo(accessToken); + } catch (Exception ex) { + LOGGER.error("An error occurs retriving the OIDC user info", ex); + return Map.of(); + } + } + + private String getAttributeAsString(Map userInfo, String attribute) { + if (isBlank(attribute)) { + return null; + } + return userInfo.containsKey(attribute) ? String.valueOf(userInfo.get(attribute)) : null; + } + + private String getEmailAttribute() { + return configurationService.getProperty("authentication-oidc.user-info.email"); + } + + private String getFirstNameAttribute() { + return configurationService.getProperty("authentication-oidc.user-info.first-name"); + } + + private String getLastNameAttribute() { + return configurationService.getProperty("authentication-oidc.user-info.last-name"); + } + + private boolean canSelfRegister() { + String canSelfRegister = configurationService.getProperty("authentication-oidc.can-self-register", "true"); + if (isBlank(canSelfRegister)) { + return true; + } + return toBoolean(canSelfRegister); + } + + public OidcClient getOidcClient() { + return this.oidcClient; + } + + public void setOidcClient(OidcClient oidcClient) { + this.oidcClient = oidcClient; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java new file mode 100644 index 0000000000..20656896eb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java @@ -0,0 +1,310 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.authenticate; + +import static java.lang.String.format; +import static java.net.URLEncoder.encode; +import static org.apache.commons.lang.BooleanUtils.toBoolean; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.dspace.content.Item.ANY; + +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.orcid.client.OrcidClient; +import org.dspace.app.orcid.client.OrcidConfiguration; +import org.dspace.app.orcid.model.OrcidTokenResponseDTO; +import org.dspace.app.orcid.service.OrcidSynchronizationService; +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.app.profile.service.ResearcherProfileService; +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.services.ConfigurationService; +import org.orcid.jaxb.model.v3.release.record.Email; +import org.orcid.jaxb.model.v3.release.record.Person; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * ORCID authentication for DSpace. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OrcidAuthenticationBean implements AuthenticationMethod { + + public static final String ORCID_AUTH_ATTRIBUTE = "orcid-authentication"; + + private final static Logger LOGGER = LoggerFactory.getLogger(OrcidAuthenticationBean.class); + + private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s"; + + @Autowired + private OrcidClient orcidClient; + + @Autowired + private OrcidConfiguration orcidConfiguration; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private EPersonService ePersonService; + + @Autowired + private ResearcherProfileService researcherProfileService; + + @Autowired + private OrcidSynchronizationService orcidSynchronizationService; + + @Override + public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request) + throws SQLException { + + if (request == null) { + LOGGER.warn("Unable to authenticate using ORCID because the request object is null."); + return BAD_ARGS; + } + + if (request.getAttribute(ORCID_AUTH_ATTRIBUTE) == null) { + return NO_SUCH_USER; + } + + String code = (String) request.getParameter("code"); + if (StringUtils.isEmpty(code)) { + LOGGER.warn("The incoming request has not code parameter"); + return NO_SUCH_USER; + } + + return authenticateWithOrcid(context, code, request); + } + + @Override + public String loginPageURL(Context context, HttpServletRequest request, HttpServletResponse response) { + + String authorizeUrl = orcidConfiguration.getAuthorizeEndpointUrl(); + String clientId = orcidConfiguration.getClientId(); + String redirectUri = orcidConfiguration.getRedirectUrl(); + String scopes = String.join("+", orcidConfiguration.getScopes()); + + if (StringUtils.isAnyBlank(authorizeUrl, clientId, redirectUri, scopes)) { + LOGGER.error("Missing mandatory configuration properties for OrcidAuthentication"); + return ""; + } + + try { + return format(LOGIN_PAGE_URL_FORMAT, authorizeUrl, clientId, scopes, encode(redirectUri, "UTF-8")); + } catch (UnsupportedEncodingException e) { + LOGGER.error(e.getMessage(), e); + return ""; + } + + } + + @Override + public boolean canSelfRegister(Context context, HttpServletRequest request, String username) throws SQLException { + return canSelfRegister(); + } + + @Override + public void initEPerson(Context context, HttpServletRequest request, EPerson eperson) throws SQLException { + + } + + @Override + public boolean allowSetPassword(Context context, HttpServletRequest request, String username) throws SQLException { + return false; + } + + @Override + public boolean isImplicit() { + return false; + } + + @Override + public List getSpecialGroups(Context context, HttpServletRequest request) throws SQLException { + return Collections.emptyList(); + } + + @Override + public String getName() { + return "orcid"; + } + + private int authenticateWithOrcid(Context context, String code, HttpServletRequest request) throws SQLException { + OrcidTokenResponseDTO token = getOrcidAccessToken(code); + if (token == null) { + return NO_SUCH_USER; + } + + String orcid = token.getOrcid(); + + EPerson ePerson = ePersonService.findByNetid(context, orcid); + if (ePerson != null) { + return ePerson.canLogIn() ? logInEPerson(context, token, ePerson) : BAD_ARGS; + } + + Person person = getPersonFromOrcid(token); + if (person == null) { + return NO_SUCH_USER; + } + + String email = getEmail(person).orElse(null); + + ePerson = ePersonService.findByEmail(context, email); + if (ePerson != null) { + return ePerson.canLogIn() ? logInEPerson(context, token, ePerson) : BAD_ARGS; + } + + return canSelfRegister() ? registerNewEPerson(context, person, token) : NO_SUCH_USER; + + } + + private int logInEPerson(Context context, OrcidTokenResponseDTO token, EPerson ePerson) + throws SQLException { + + context.setCurrentUser(ePerson); + + setOrcidMetadataOnEPerson(context, ePerson, token); + + ResearcherProfile profile = findProfile(context, ePerson); + if (profile != null) { + orcidSynchronizationService.linkProfile(context, profile.getItem(), token); + } + + return SUCCESS; + + } + + private ResearcherProfile findProfile(Context context, EPerson ePerson) throws SQLException { + try { + return researcherProfileService.findById(context, ePerson.getID()); + } catch (AuthorizeException e) { + throw new RuntimeException(e); + } + } + + private int registerNewEPerson(Context context, Person person, OrcidTokenResponseDTO token) throws SQLException { + + try { + context.turnOffAuthorisationSystem(); + + String orcid = token.getOrcid(); + + EPerson eperson = ePersonService.create(context); + + eperson.setNetid(orcid); + eperson.setEmail(getEmail(person).orElse(orcid)); + eperson.setFirstName(context, getFirstName(person)); + eperson.setLastName(context, getLastName(person)); + eperson.setCanLogIn(true); + eperson.setSelfRegistered(true); + + setOrcidMetadataOnEPerson(context, eperson, token); + + ePersonService.update(context, eperson); + context.setCurrentUser(eperson); + context.dispatchEvents(); + + return SUCCESS; + + } catch (Exception ex) { + LOGGER.error("An error occurs registering a new EPerson from ORCID", ex); + context.rollback(); + return NO_SUCH_USER; + } finally { + context.restoreAuthSystemState(); + } + } + + private void setOrcidMetadataOnEPerson(Context context, EPerson person, OrcidTokenResponseDTO token) + throws SQLException { + + String orcid = token.getOrcid(); + String accessToken = token.getAccessToken(); + String refreshToken = token.getRefreshToken(); + String[] scopes = token.getScopeAsArray(); + + ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", null, null, orcid); + ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", "access-token", null, accessToken); + ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", "refresh-token", null, refreshToken); + ePersonService.clearMetadata(context, person, "eperson", "orcid", "scope", ANY); + for (String scope : scopes) { + ePersonService.addMetadata(context, person, "eperson", "orcid", "scope", null, scope); + } + + } + + private Person getPersonFromOrcid(OrcidTokenResponseDTO token) { + try { + return orcidClient.getPerson(token.getAccessToken(), token.getOrcid()); + } catch (Exception ex) { + LOGGER.error("An error occurs retriving the ORCID record with id " + token.getOrcid(), ex); + return null; + } + } + + private Optional getEmail(Person person) { + List emails = person.getEmails() != null ? person.getEmails().getEmails() : Collections.emptyList(); + if (CollectionUtils.isEmpty(emails)) { + return Optional.empty(); + } + return Optional.ofNullable(emails.get(0).getEmail()); + } + + private String getFirstName(Person person) { + return Optional.ofNullable(person.getName()) + .map(name -> name.getGivenNames()) + .map(givenNames -> givenNames.getContent()) + .orElseThrow(() -> new IllegalStateException("The found ORCID person has no first name")); + } + + private String getLastName(Person person) { + return Optional.ofNullable(person.getName()) + .map(name -> name.getFamilyName()) + .map(givenNames -> givenNames.getContent()) + .orElseThrow(() -> new IllegalStateException("The found ORCID person has no last name")); + } + + private boolean canSelfRegister() { + String canSelfRegister = configurationService.getProperty("authentication-oidc.can-self-register", "true"); + if (isBlank(canSelfRegister)) { + return true; + } + return toBoolean(canSelfRegister); + } + + private OrcidTokenResponseDTO getOrcidAccessToken(String code) { + try { + return orcidClient.getAccessToken(code); + } catch (Exception ex) { + LOGGER.error("An error occurs retriving the ORCID access_token", ex); + return null; + } + } + + public OrcidClient getOrcidClient() { + return orcidClient; + } + + public void setOrcidClient(OrcidClient orcidClient) { + this.orcidClient = orcidClient; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/OidcClient.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/OidcClient.java new file mode 100644 index 0000000000..8656574ee9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/OidcClient.java @@ -0,0 +1,42 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.authenticate.oidc; + +import java.util.Map; + +import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO; + +/** + * Client to interact with the configured OIDC provider. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public interface OidcClient { + + /** + * Exchange the authorization code for a 3-legged access token. The + * authorization code expires upon use. + * + * @param code the authorization code + * @return the OIDC token + * @throws OidcClientException if some error occurs during the exchange + */ + OidcTokenResponseDTO getAccessToken(String code) throws OidcClientException; + + /** + * Retrieve the info related to the user associated with the given accessToken + * from the user info endpoint. + * + * @param accessToken the access token + * @return a map with the user infos + * @throws OidcClientException if some error occurs during the exchange + */ + Map getUserInfo(String accessToken) throws OidcClientException; + +} diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/OidcClientException.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/OidcClientException.java new file mode 100644 index 0000000000..4f9bc5fdc9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/OidcClientException.java @@ -0,0 +1,34 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.authenticate.oidc; + +/** + * Exception throwable from class that implements {@link OidcClient} in case of + * error response from the OIDC provider. + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OidcClientException extends RuntimeException { + + private static final long serialVersionUID = -7618061110212398216L; + + private int status = 0; + + public OidcClientException(int status, String content) { + super(content); + this.status = status; + } + + public OidcClientException(Throwable cause) { + super(cause); + } + + public int getStatus() { + return this.status; + } +} diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java new file mode 100644 index 0000000000..af482ec18d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java @@ -0,0 +1,148 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.authenticate.oidc.impl; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.dspace.authenticate.oidc.OidcClient; +import org.dspace.authenticate.oidc.OidcClientException; +import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO; +import org.dspace.services.ConfigurationService; +import org.dspace.util.ThrowingSupplier; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link OidcClient}. + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OidcClientImpl implements OidcClient { + + @Autowired + private ConfigurationService configurationService; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public OidcTokenResponseDTO getAccessToken(String code) throws OidcClientException { + List params = new ArrayList(); + params.add(new BasicNameValuePair("code", code)); + params.add(new BasicNameValuePair("grant_type", "authorization_code")); + params.add(new BasicNameValuePair("client_id", getClientId())); + params.add(new BasicNameValuePair("client_secret", getClientSecret())); + params.add(new BasicNameValuePair("redirect_uri", getRedirectUrl())); + + HttpUriRequest httpUriRequest = RequestBuilder.post(getTokenEndpointUrl()) + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .addHeader("Accept", "application/json") + .setEntity(new UrlEncodedFormEntity(params, Charset.defaultCharset())) + .build(); + + return executeAndParseJson(httpUriRequest, OidcTokenResponseDTO.class); + + } + + @Override + @SuppressWarnings("unchecked") + public Map getUserInfo(String accessToken) throws OidcClientException { + + HttpUriRequest httpUriRequest = RequestBuilder.get(getUserInfoEndpointUrl()) + .addHeader("Authorization", "Bearer " + accessToken) + .build(); + + return executeAndParseJson(httpUriRequest, Map.class); + } + + private T executeAndParseJson(HttpUriRequest httpUriRequest, Class clazz) { + + HttpClient client = HttpClientBuilder.create().build(); + + return executeAndReturns(() -> { + + HttpResponse response = client.execute(httpUriRequest); + + if (isNotSuccessfull(response)) { + throw new OidcClientException(getStatusCode(response), formatErrorMessage(response)); + } + + return objectMapper.readValue(getContent(response), clazz); + + }); + + } + + private T executeAndReturns(ThrowingSupplier supplier) { + try { + return supplier.get(); + } catch (OidcClientException ex) { + throw ex; + } catch (Exception ex) { + throw new OidcClientException(ex); + } + } + + private String formatErrorMessage(HttpResponse response) { + try { + return IOUtils.toString(response.getEntity().getContent(), Charset.defaultCharset()); + } catch (UnsupportedOperationException | IOException e) { + return "Generic error"; + } + } + + private boolean isNotSuccessfull(HttpResponse response) { + int statusCode = getStatusCode(response); + return statusCode < 200 || statusCode > 299; + } + + private int getStatusCode(HttpResponse response) { + return response.getStatusLine().getStatusCode(); + } + + private String getContent(HttpResponse response) throws UnsupportedOperationException, IOException { + HttpEntity entity = response.getEntity(); + return entity != null ? IOUtils.toString(entity.getContent(), UTF_8.name()) : null; + } + + private String getClientId() { + return configurationService.getProperty("authentication-oidc.client-id"); + } + + private String getClientSecret() { + return configurationService.getProperty("authentication-oidc.client-secret"); + } + + private String getTokenEndpointUrl() { + return configurationService.getProperty("authentication-oidc.token-endpoint"); + } + + private String getUserInfoEndpointUrl() { + return configurationService.getProperty("authentication-oidc.user-info-endpoint"); + } + + private String getRedirectUrl() { + return configurationService.getProperty("authentication-oidc.redirect-url"); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/model/OidcTokenResponseDTO.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/model/OidcTokenResponseDTO.java new file mode 100644 index 0000000000..d2c2efc987 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/model/OidcTokenResponseDTO.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.authenticate.oidc.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class map the response from and OpenID Connect token endpoint. + * {@link https://openid.net/specs/openid-connect-core-1_0.html} + * + * Response example: + * + * { "access_token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsIm9yZ...", "id_token": + * "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGki...", "token_type": "bearer", + * "expires_in": 28800, "scope": "pgc-role email openid profile" } + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * + */ +public class OidcTokenResponseDTO { + + /** + * The access token release by the authorization server this is the most + * relevant item, because it allow the server to access to the user resources as + * defined in the scopes {@link https://tools.ietf.org/html/rfc6749#section-1.4} + */ + @JsonProperty("access_token") + private String accessToken; + + /** + * The id token as defined in the OpenID connect standard + * {@link https://openid.net/specs/openid-connect-core-1_0.html#IDToken} + */ + @JsonProperty("id_token") + private String idToken; + + /** + * The refresh token as defined in the OAuth standard + * {@link https://tools.ietf.org/html/rfc6749#section-1.5} + */ + @JsonProperty("refresh_token") + private String refreshToken; + + /** + * It will be "bearer" + */ + @JsonProperty("token_type") + private String tokenType; + + /** + * The expiration timestamp in millis + */ + @JsonProperty("expires_in") + private Long expiresIn; + + /** + * List of scopes {@link https://tools.ietf.org/html/rfc6749#section-3.3} + */ + @JsonProperty("scope") + private String scope; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getIdToken() { + return idToken; + } + + public void setIdToken(String idToken) { + this.idToken = idToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public Long getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Long expiresIn) { + this.expiresIn = expiresIn; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java new file mode 100644 index 0000000000..7a94e527e3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.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; + +import java.io.IOException; +import java.util.List; +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.AuthnRest; +import org.dspace.services.ConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.Link; +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; + +/** + * Rest controller that handles redirect after OIDC authentication succeded. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + */ +@RestController +@RequestMapping(value = "/api/" + AuthnRest.CATEGORY + "/oidc") +public class OidcRestController { + + private static final Logger log = LoggerFactory.getLogger(OidcRestController.class); + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private DiscoverableEndpointsService discoverableEndpointsService; + + @PostConstruct + public void afterPropertiesSet() { + discoverableEndpointsService.register(this, List.of(new Link("/api/" + AuthnRest.CATEGORY, "oidc"))); + } + + @RequestMapping(method = RequestMethod.GET) + public void oidc(HttpServletResponse response, + @RequestParam(name = "redirectUrl", required = false) String redirectUrl) throws IOException { + if (StringUtils.isBlank(redirectUrl)) { + redirectUrl = configurationService.getProperty("dspace.ui.url"); + } + log.info("Redirecting to " + redirectUrl); + response.sendRedirect(redirectUrl); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java new file mode 100644 index 0000000000..5cdd3b982a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.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.security; + +import static org.dspace.authenticate.OidcAuthenticationBean.OIDC_AUTH_ATTRIBUTE; + +import java.io.IOException; +import java.util.ArrayList; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * This class will filter openID Connect requests and try and authenticate them. + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ + +public class OidcAuthenticationFilter extends StatelessLoginFilter { + + public OidcAuthenticationFilter(String url, AuthenticationManager authenticationManager, + RestAuthenticationService restAuthenticationService) { + super(url, authenticationManager, restAuthenticationService); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) + throws AuthenticationException { + req.setAttribute(OIDC_AUTH_ATTRIBUTE, OIDC_AUTH_ATTRIBUTE); + return authenticationManager.authenticate(new DSpaceAuthentication(null, null, new ArrayList<>())); + } + + @Override + protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, + Authentication auth) throws IOException, ServletException { + restAuthenticationService.addAuthenticationDataForUser(req, res, (DSpaceAuthentication) auth, true); + chain.doFilter(req, res); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index bf729b5433..fb332cc8aa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -137,6 +137,14 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { .addFilterBefore(new ShibbolethLoginFilter("/api/authn/shibboleth", authenticationManager(), restAuthenticationService), LogoutFilter.class) + //Add a filter before our ORCID endpoints to do the authentication based on the data in the + // HTTP request + .addFilterBefore(new OrcidAuthenticationFilter("/api/authn/orcid", authenticationManager(), + restAuthenticationService), + LogoutFilter.class) + .addFilterBefore(new OidcAuthenticationFilter("/api/authn/oidc", authenticationManager(), + restAuthenticationService), + LogoutFilter.class) // Add a custom Token based authentication filter based on the token previously given to the client // before each URL .addFilterBefore(new StatelessAuthenticationFilter(authenticationManager(), restAuthenticationService, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/OidcAuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/OidcAuthenticationRestControllerIT.java new file mode 100644 index 0000000000..aa1ecd5ebf --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/OidcAuthenticationRestControllerIT.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; + +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.text.ParseException; +import java.util.Map; +import javax.servlet.http.Cookie; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jwt.SignedJWT; +import org.dspace.app.rest.model.AuthnRest; +import org.dspace.app.rest.security.jwt.EPersonClaimProvider; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authenticate.OidcAuthenticationBean; +import org.dspace.authenticate.oidc.OidcClient; +import org.dspace.authenticate.oidc.OidcClientException; +import org.dspace.authenticate.oidc.model.OidcTokenResponseDTO; +import org.dspace.builder.EPersonBuilder; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.dspace.services.ConfigurationService; +import org.dspace.util.UUIDUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MvcResult; + +/** + * Integration tests for {@link OidcAuthenticationRestController}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class OidcAuthenticationRestControllerIT extends AbstractControllerIntegrationTest { + + private final static String CODE = "123456"; + private final static String EMAIL = "email"; + private final static String FIRST_NAME = "first_name"; + private final static String LAST_NAME = "last_name"; + + private final static String ACCESS_TOKEN = "c41e37e5-c2de-4177-91d6-ed9e9d1f31bf"; + private final static String REFRESH_TOKEN = "0062a9eb-d4ec-4d94-9491-95dd75376d3e"; + private final static String[] OIDC_SCOPES = { "FirstScope", "SecondScope" }; + + private OidcClient originalOidcClient; + + private OidcClient oidcClientMock = mock(OidcClient.class); + + private EPerson createdEperson; + + @Autowired + private OidcAuthenticationBean oidcAuthentication; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private EPersonService ePersonService; + + @Before + public void setup() { + originalOidcClient = oidcAuthentication.getOidcClient(); + oidcAuthentication.setOidcClient(oidcClientMock); + + configurationService.setProperty("authentication-oidc.user-info.email", EMAIL); + configurationService.setProperty("authentication-oidc.user-info.first-name", FIRST_NAME); + configurationService.setProperty("authentication-oidc.user-info.last-name", LAST_NAME); + + configurationService.setProperty("plugin.sequence.org.dspace.authenticate.AuthenticationMethod", + asList("org.dspace.authenticate.OidcAuthentication", "org.dspace.authenticate.PasswordAuthentication")); + } + + @After + public void after() throws Exception { + oidcAuthentication.setOidcClient(originalOidcClient); + if (createdEperson != null) { + context.turnOffAuthorisationSystem(); + ePersonService.delete(context, createdEperson); + context.restoreAuthSystemState(); + } + } + + @Test + public void testEPersonCreationViaOidcLogin() throws Exception { + + when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN)); + when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it", "Test", "User")); + + MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc") + .param("code", CODE)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url"))) + .andExpect(cookie().exists("Authorization-cookie")) + .andReturn(); + + verify(oidcClientMock).getAccessToken(CODE); + verify(oidcClientMock).getUserInfo(ACCESS_TOKEN); + verifyNoMoreInteractions(oidcClientMock); + + String ePersonId = getEPersonIdFromAuthorizationCookie(mvcResult); + + createdEperson = ePersonService.find(context, UUIDUtils.fromString(ePersonId)); + assertThat(createdEperson, notNullValue()); + assertThat(createdEperson.getEmail(), equalTo("test@email.it")); + assertThat(createdEperson.getFullName(), equalTo("Test User")); + assertThat(createdEperson.getNetid(), equalTo("test@email.it")); + assertThat(createdEperson.canLogIn(), equalTo(true)); + + } + + @Test + public void testEPersonCreationViaOidcLoginWithoutEmail() throws Exception { + + when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN)); + when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it")); + + MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc") + .param("code", CODE)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url"))) + .andExpect(cookie().exists("Authorization-cookie")) + .andReturn(); + + verify(oidcClientMock).getAccessToken(CODE); + verify(oidcClientMock).getUserInfo(ACCESS_TOKEN); + verifyNoMoreInteractions(oidcClientMock); + + String ePersonId = getEPersonIdFromAuthorizationCookie(mvcResult); + + createdEperson = ePersonService.find(context, UUIDUtils.fromString(ePersonId)); + assertThat(createdEperson, notNullValue()); + } + + @Test + public void testWithoutSelfRegistrationEnabled() throws Exception { + + configurationService.setProperty("authentication-oidc.can-self-register", "false"); + when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN)); + when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it")); + + MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc") + .param("code", CODE)) + .andExpect(status().isUnauthorized()) + .andExpect(cookie().doesNotExist("Authorization-cookie")) + .andExpect(header().exists("WWW-Authenticate")) + .andReturn(); + + String authenticateHeader = mvcResult.getResponse().getHeader("WWW-Authenticate"); + assertThat(authenticateHeader, containsString("oidc realm=\"DSpace REST API\"")); + + verify(oidcClientMock).getAccessToken(CODE); + verify(oidcClientMock).getUserInfo(ACCESS_TOKEN); + verifyNoMoreInteractions(oidcClientMock); + + } + + @Test + public void testWithoutAuthorizationCode() throws Exception { + + getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc")) + .andExpect(status().isUnauthorized()) + .andExpect(cookie().doesNotExist("Authorization-cookie")) + .andExpect(header().exists("WWW-Authenticate")); + + verifyNoInteractions(oidcClientMock); + + } + + @Test + public void testEPersonLoggedInByEmail() throws Exception { + + when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN)); + when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it")); + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withEmail("test@email.it") + .withNameInMetadata("Test", "User") + .withCanLogin(true) + .build(); + + context.restoreAuthSystemState(); + + MvcResult mvcResult = getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc") + .param("code", CODE)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url"))) + .andExpect(cookie().exists("Authorization-cookie")) + .andReturn(); + + verify(oidcClientMock).getAccessToken(CODE); + verify(oidcClientMock).getUserInfo(ACCESS_TOKEN); + verifyNoMoreInteractions(oidcClientMock); + + String ePersonId = getEPersonIdFromAuthorizationCookie(mvcResult); + assertThat(ePersonId, notNullValue()); + assertThat(ePersonId, equalTo(ePerson.getID().toString())); + + } + + @Test + public void testEPersonCannotLogInByEmail() throws Exception { + + when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN)); + when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenReturn(buildUserInfo("test@email.it")); + + context.turnOffAuthorisationSystem(); + + EPersonBuilder.createEPerson(context) + .withEmail("test@email.it") + .withNameInMetadata("Test", "User") + .withCanLogin(false) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc") + .param("code", CODE)) + .andExpect(status().isUnauthorized()) + .andExpect(cookie().doesNotExist("Authorization-cookie")) + .andExpect(header().exists("WWW-Authenticate")); + + verify(oidcClientMock).getAccessToken(CODE); + verify(oidcClientMock).getUserInfo(ACCESS_TOKEN); + verifyNoMoreInteractions(oidcClientMock); + + } + + @Test + public void testNoAuthenticationIfAnErrorOccursRetrivingOidcToken() throws Exception { + + when(oidcClientMock.getAccessToken(CODE)).thenThrow(new OidcClientException(500, "internal error")); + + context.turnOffAuthorisationSystem(); + + EPersonBuilder.createEPerson(context) + .withEmail("test@email.it") + .withNameInMetadata("Test", "User") + .withCanLogin(false) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc") + .param("code", CODE)) + .andExpect(status().isUnauthorized()) + .andExpect(cookie().doesNotExist("Authorization-cookie")) + .andExpect(header().exists("WWW-Authenticate")); + + verify(oidcClientMock).getAccessToken(CODE); + verifyNoMoreInteractions(oidcClientMock); + + } + + @Test + public void testNoAuthenticationIfAnErrorOccursRetrivingOidcPerson() throws Exception { + + when(oidcClientMock.getAccessToken(CODE)).thenReturn(buildOidcTokenResponse(ACCESS_TOKEN)); + when(oidcClientMock.getUserInfo(ACCESS_TOKEN)).thenThrow(new OidcClientException(500, "Internal Error")); + + context.turnOffAuthorisationSystem(); + + EPersonBuilder.createEPerson(context) + .withEmail("test@email.it") + .withNameInMetadata("Test", "User") + .withCanLogin(false) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/" + AuthnRest.CATEGORY + "/oidc") + .param("code", CODE)) + .andExpect(status().isUnauthorized()) + .andExpect(cookie().doesNotExist("Authorization-cookie")) + .andExpect(header().exists("WWW-Authenticate")); + + verify(oidcClientMock).getAccessToken(CODE); + verify(oidcClientMock).getUserInfo(ACCESS_TOKEN); + verifyNoMoreInteractions(oidcClientMock); + + } + + private OidcTokenResponseDTO buildOidcTokenResponse(String accessToken) { + OidcTokenResponseDTO token = new OidcTokenResponseDTO(); + token.setAccessToken(accessToken); + token.setTokenType("Bearer"); + token.setRefreshToken(REFRESH_TOKEN); + token.setScope(String.join(" ", OIDC_SCOPES)); + return token; + } + + private Map buildUserInfo(String email) { + return Map.of(EMAIL, email); + } + + private Map buildUserInfo(String email, String firstName, String lastName) { + return Map.of(EMAIL, email, FIRST_NAME, firstName, LAST_NAME, lastName); + } + + private String getEPersonIdFromAuthorizationCookie(MvcResult mvcResult) throws ParseException, JOSEException { + Cookie authorizationCookie = mvcResult.getResponse().getCookie("Authorization-cookie"); + SignedJWT jwt = SignedJWT.parse(authorizationCookie.getValue()); + return (String) jwt.getJWTClaimsSet().getClaim(EPersonClaimProvider.EPERSON_ID); + } +} diff --git a/dspace/config/modules/authentication-oidc.cfg b/dspace/config/modules/authentication-oidc.cfg new file mode 100644 index 0000000000..55ce150e98 --- /dev/null +++ b/dspace/config/modules/authentication-oidc.cfg @@ -0,0 +1,42 @@ +#---------------------------------------------------------------# +#---------------OIDC AUTHENTICATION CONFIGURATIONS--------------# +#---------------------------------------------------------------# +# Configuration properties used by the CAS OIDC # +# Authentication plugin, when it is enabled. # +#---------------------------------------------------------------# + +# The domain of the OpenID Connect server +authentication-oidc.auth-server-domain = + +# The URL of the Token endpoint +authentication-oidc.token-endpoint = + +# The URL of the Authorize endpoint +authentication-oidc.authorize-endpoint = + +# The URL of the Introspect endpoint +authentication-oidc.user-info-endpoint = + +# The registered client id +authentication-oidc.client-id = + +# The registered client secret +authentication-oidc.client-secret = + +# The redirect url +authentication-oidc.redirect-url = ${dspace.server.url}/api/authn/oidc + +# The scopes to request +authentication-oidc.scopes = + +#Specify if the user can self register using OIDC (true|false). If not specified, true is assumed +authentication-oidc.can-self-register = + +#Specify the attribute present in the user info json related to the user's email +authentication-oidc.user-info.email = + +#Specify the attribute present in the user info json related to the user's first name +authentication-oidc.user-info.first-name = + +#Specify the attribute present in the user info json related to the user's last name +authentication-oidc.user-info.last-name = \ No newline at end of file diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index 42031bd7a8..7cc6a13b35 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -24,6 +24,13 @@ # * X.509 Certificate Authentication # Plugin class: org.dspace.authenticate.X509Authentication # Configuration file: authentication-x509.cfg +# * ORCID Authentication +# Plugin class: org.dspace.authenticate.OrcidAuthentication +# Configuration file: authentication-orcid.cfg +# * OIDC Authentication +# Plugin class: org.dspace.authenticate.OidcAuthentication +# Configuration file: authentication-oidc.cfg + # # One or more of the above plugins can be enabled by listing its plugin class in # the below setting. To configure the enabled plugin(s) visit the configuration file(s) @@ -45,6 +52,12 @@ # X.509 certificate authentication. See authentication-x509.cfg for default configuration. #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.X509Authentication +# ORCID authentication. See authentication-orcid.cfg for default configuration. +#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OrcidAuthentication + +# OIDC authentication. See authentication-oidc.cfg for default configuration. +#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OidcAuthentication + # Authentication by Password (encrypted in DSpace's database). See authentication-password.cfg for default configuration. # Enabled by default (to disable, either comment out, or define a new list of AuthenticationMethod plugins in your local.cfg) plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.PasswordAuthentication diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 526c448b46..9ab2021ab2 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -134,5 +134,28 @@ + + + + + + + + + + + + + + + + + + + + + + + From 1d89538cb1818318c6179cdfebebe0137c02c68e Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 15 Oct 2021 14:56:30 +0200 Subject: [PATCH 0646/1254] [DSC-196] Fix tests --- .../java/org/dspace/authenticate/OrcidAuthenticationBean.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java index 20656896eb..1ba537f79a 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java @@ -283,7 +283,7 @@ public class OrcidAuthenticationBean implements AuthenticationMethod { } private boolean canSelfRegister() { - String canSelfRegister = configurationService.getProperty("authentication-oidc.can-self-register", "true"); + String canSelfRegister = configurationService.getProperty("authentication-orcid.can-self-register", "true"); if (isBlank(canSelfRegister)) { return true; } From 2d05ff392c2f96a6ee1533421b528528914bb03f Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Thu, 28 Oct 2021 11:32:22 -0500 Subject: [PATCH 0647/1254] Add missing util: ThrowingSupplier --- .../org/dspace/util/ThrowingSupplier.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/util/ThrowingSupplier.java diff --git a/dspace-api/src/main/java/org/dspace/util/ThrowingSupplier.java b/dspace-api/src/main/java/org/dspace/util/ThrowingSupplier.java new file mode 100644 index 0000000000..a00e675f25 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/ThrowingSupplier.java @@ -0,0 +1,26 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.util; + +/** + * Functional interface that can be used to returns an object and potentially + * throws a Exception. + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@FunctionalInterface +public interface ThrowingSupplier { + + /** + * Returns an object. + * + * @return an object + * @throws E if some error occurs + */ + T get() throws E; +} From 61d65d1e74ed4b55a99e341d2d51abbb4ad9cdc2 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Thu, 28 Oct 2021 11:34:12 -0500 Subject: [PATCH 0648/1254] Ignoring jenv .java-version files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index fc8dab2da9..2fcb46b993 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,7 @@ nb-configuration.xml ##Ignore JRebel project configuration rebel.xml + + +## Ignore jenv configuration +.java-version From d2a13527ffbdff1da50aff3ba75fb6ec6db76151 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Thu, 28 Oct 2021 11:41:40 -0500 Subject: [PATCH 0649/1254] Add OrcidAuthenticationFiler --- .../security/OrcidAuthenticationFilter.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidAuthenticationFilter.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidAuthenticationFilter.java new file mode 100644 index 0000000000..1b25002162 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidAuthenticationFilter.java @@ -0,0 +1,54 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 java.util.Collections.emptyList; +import static org.dspace.authenticate.OrcidAuthenticationBean.ORCID_AUTH_ATTRIBUTE; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * This class will filter ORCID requests and try and authenticate them. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + */ + +public class OrcidAuthenticationFilter extends StatelessLoginFilter { + + public OrcidAuthenticationFilter(String url, AuthenticationManager authenticationManager, + RestAuthenticationService restAuthenticationService) { + super(url, authenticationManager, restAuthenticationService); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) + throws AuthenticationException { + + req.setAttribute(ORCID_AUTH_ATTRIBUTE, ORCID_AUTH_ATTRIBUTE); + return authenticationManager.authenticate(new DSpaceAuthentication(null, null, emptyList())); + + } + + @Override + protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, + Authentication auth) throws IOException, ServletException { + + restAuthenticationService.addAuthenticationDataForUser(req, res, (DSpaceAuthentication) auth, true); + chain.doFilter(req, res); + + } + +} From 2987fc2f9a7585a68a3c014eae5dc67b7c71400e Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Fri, 29 Oct 2021 10:39:30 -0500 Subject: [PATCH 0650/1254] Suggested changes from Luca Giamminonni: remove ORCID AuthN --- .../authenticate/OidcAuthenticationBean.java | 1 - .../authenticate/OrcidAuthenticationBean.java | 310 ------------------ .../security/OrcidAuthenticationFilter.java | 54 --- .../security/WebSecurityConfiguration.java | 5 +- 4 files changed, 1 insertion(+), 369 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidAuthenticationFilter.java diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java index 229457f839..a0b1fc8dd9 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java @@ -197,7 +197,6 @@ public class OidcAuthenticationBean implements AuthenticationMethod { } catch (Exception ex) { LOGGER.error("An error occurs registering a new EPerson from OIDC", ex); - context.rollback(); return NO_SUCH_USER; } finally { context.restoreAuthSystemState(); diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java deleted file mode 100644 index 1ba537f79a..0000000000 --- a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java +++ /dev/null @@ -1,310 +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.authenticate; - -import static java.lang.String.format; -import static java.net.URLEncoder.encode; -import static org.apache.commons.lang.BooleanUtils.toBoolean; -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.dspace.content.Item.ANY; - -import java.io.UnsupportedEncodingException; -import java.sql.SQLException; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.orcid.client.OrcidClient; -import org.dspace.app.orcid.client.OrcidConfiguration; -import org.dspace.app.orcid.model.OrcidTokenResponseDTO; -import org.dspace.app.orcid.service.OrcidSynchronizationService; -import org.dspace.app.profile.ResearcherProfile; -import org.dspace.app.profile.service.ResearcherProfileService; -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.services.ConfigurationService; -import org.orcid.jaxb.model.v3.release.record.Email; -import org.orcid.jaxb.model.v3.release.record.Person; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * ORCID authentication for DSpace. - * - * @author Luca Giamminonni (luca.giamminonni at 4science.it) - * - */ -public class OrcidAuthenticationBean implements AuthenticationMethod { - - public static final String ORCID_AUTH_ATTRIBUTE = "orcid-authentication"; - - private final static Logger LOGGER = LoggerFactory.getLogger(OrcidAuthenticationBean.class); - - private final static String LOGIN_PAGE_URL_FORMAT = "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s"; - - @Autowired - private OrcidClient orcidClient; - - @Autowired - private OrcidConfiguration orcidConfiguration; - - @Autowired - private ConfigurationService configurationService; - - @Autowired - private EPersonService ePersonService; - - @Autowired - private ResearcherProfileService researcherProfileService; - - @Autowired - private OrcidSynchronizationService orcidSynchronizationService; - - @Override - public int authenticate(Context context, String username, String password, String realm, HttpServletRequest request) - throws SQLException { - - if (request == null) { - LOGGER.warn("Unable to authenticate using ORCID because the request object is null."); - return BAD_ARGS; - } - - if (request.getAttribute(ORCID_AUTH_ATTRIBUTE) == null) { - return NO_SUCH_USER; - } - - String code = (String) request.getParameter("code"); - if (StringUtils.isEmpty(code)) { - LOGGER.warn("The incoming request has not code parameter"); - return NO_SUCH_USER; - } - - return authenticateWithOrcid(context, code, request); - } - - @Override - public String loginPageURL(Context context, HttpServletRequest request, HttpServletResponse response) { - - String authorizeUrl = orcidConfiguration.getAuthorizeEndpointUrl(); - String clientId = orcidConfiguration.getClientId(); - String redirectUri = orcidConfiguration.getRedirectUrl(); - String scopes = String.join("+", orcidConfiguration.getScopes()); - - if (StringUtils.isAnyBlank(authorizeUrl, clientId, redirectUri, scopes)) { - LOGGER.error("Missing mandatory configuration properties for OrcidAuthentication"); - return ""; - } - - try { - return format(LOGIN_PAGE_URL_FORMAT, authorizeUrl, clientId, scopes, encode(redirectUri, "UTF-8")); - } catch (UnsupportedEncodingException e) { - LOGGER.error(e.getMessage(), e); - return ""; - } - - } - - @Override - public boolean canSelfRegister(Context context, HttpServletRequest request, String username) throws SQLException { - return canSelfRegister(); - } - - @Override - public void initEPerson(Context context, HttpServletRequest request, EPerson eperson) throws SQLException { - - } - - @Override - public boolean allowSetPassword(Context context, HttpServletRequest request, String username) throws SQLException { - return false; - } - - @Override - public boolean isImplicit() { - return false; - } - - @Override - public List getSpecialGroups(Context context, HttpServletRequest request) throws SQLException { - return Collections.emptyList(); - } - - @Override - public String getName() { - return "orcid"; - } - - private int authenticateWithOrcid(Context context, String code, HttpServletRequest request) throws SQLException { - OrcidTokenResponseDTO token = getOrcidAccessToken(code); - if (token == null) { - return NO_SUCH_USER; - } - - String orcid = token.getOrcid(); - - EPerson ePerson = ePersonService.findByNetid(context, orcid); - if (ePerson != null) { - return ePerson.canLogIn() ? logInEPerson(context, token, ePerson) : BAD_ARGS; - } - - Person person = getPersonFromOrcid(token); - if (person == null) { - return NO_SUCH_USER; - } - - String email = getEmail(person).orElse(null); - - ePerson = ePersonService.findByEmail(context, email); - if (ePerson != null) { - return ePerson.canLogIn() ? logInEPerson(context, token, ePerson) : BAD_ARGS; - } - - return canSelfRegister() ? registerNewEPerson(context, person, token) : NO_SUCH_USER; - - } - - private int logInEPerson(Context context, OrcidTokenResponseDTO token, EPerson ePerson) - throws SQLException { - - context.setCurrentUser(ePerson); - - setOrcidMetadataOnEPerson(context, ePerson, token); - - ResearcherProfile profile = findProfile(context, ePerson); - if (profile != null) { - orcidSynchronizationService.linkProfile(context, profile.getItem(), token); - } - - return SUCCESS; - - } - - private ResearcherProfile findProfile(Context context, EPerson ePerson) throws SQLException { - try { - return researcherProfileService.findById(context, ePerson.getID()); - } catch (AuthorizeException e) { - throw new RuntimeException(e); - } - } - - private int registerNewEPerson(Context context, Person person, OrcidTokenResponseDTO token) throws SQLException { - - try { - context.turnOffAuthorisationSystem(); - - String orcid = token.getOrcid(); - - EPerson eperson = ePersonService.create(context); - - eperson.setNetid(orcid); - eperson.setEmail(getEmail(person).orElse(orcid)); - eperson.setFirstName(context, getFirstName(person)); - eperson.setLastName(context, getLastName(person)); - eperson.setCanLogIn(true); - eperson.setSelfRegistered(true); - - setOrcidMetadataOnEPerson(context, eperson, token); - - ePersonService.update(context, eperson); - context.setCurrentUser(eperson); - context.dispatchEvents(); - - return SUCCESS; - - } catch (Exception ex) { - LOGGER.error("An error occurs registering a new EPerson from ORCID", ex); - context.rollback(); - return NO_SUCH_USER; - } finally { - context.restoreAuthSystemState(); - } - } - - private void setOrcidMetadataOnEPerson(Context context, EPerson person, OrcidTokenResponseDTO token) - throws SQLException { - - String orcid = token.getOrcid(); - String accessToken = token.getAccessToken(); - String refreshToken = token.getRefreshToken(); - String[] scopes = token.getScopeAsArray(); - - ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", null, null, orcid); - ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", "access-token", null, accessToken); - ePersonService.setMetadataSingleValue(context, person, "eperson", "orcid", "refresh-token", null, refreshToken); - ePersonService.clearMetadata(context, person, "eperson", "orcid", "scope", ANY); - for (String scope : scopes) { - ePersonService.addMetadata(context, person, "eperson", "orcid", "scope", null, scope); - } - - } - - private Person getPersonFromOrcid(OrcidTokenResponseDTO token) { - try { - return orcidClient.getPerson(token.getAccessToken(), token.getOrcid()); - } catch (Exception ex) { - LOGGER.error("An error occurs retriving the ORCID record with id " + token.getOrcid(), ex); - return null; - } - } - - private Optional getEmail(Person person) { - List emails = person.getEmails() != null ? person.getEmails().getEmails() : Collections.emptyList(); - if (CollectionUtils.isEmpty(emails)) { - return Optional.empty(); - } - return Optional.ofNullable(emails.get(0).getEmail()); - } - - private String getFirstName(Person person) { - return Optional.ofNullable(person.getName()) - .map(name -> name.getGivenNames()) - .map(givenNames -> givenNames.getContent()) - .orElseThrow(() -> new IllegalStateException("The found ORCID person has no first name")); - } - - private String getLastName(Person person) { - return Optional.ofNullable(person.getName()) - .map(name -> name.getFamilyName()) - .map(givenNames -> givenNames.getContent()) - .orElseThrow(() -> new IllegalStateException("The found ORCID person has no last name")); - } - - private boolean canSelfRegister() { - String canSelfRegister = configurationService.getProperty("authentication-orcid.can-self-register", "true"); - if (isBlank(canSelfRegister)) { - return true; - } - return toBoolean(canSelfRegister); - } - - private OrcidTokenResponseDTO getOrcidAccessToken(String code) { - try { - return orcidClient.getAccessToken(code); - } catch (Exception ex) { - LOGGER.error("An error occurs retriving the ORCID access_token", ex); - return null; - } - } - - public OrcidClient getOrcidClient() { - return orcidClient; - } - - public void setOrcidClient(OrcidClient orcidClient) { - this.orcidClient = orcidClient; - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidAuthenticationFilter.java deleted file mode 100644 index 1b25002162..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidAuthenticationFilter.java +++ /dev/null @@ -1,54 +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 static java.util.Collections.emptyList; -import static org.dspace.authenticate.OrcidAuthenticationBean.ORCID_AUTH_ATTRIBUTE; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; - -/** - * This class will filter ORCID requests and try and authenticate them. - * - * @author Luca Giamminonni (luca.giamminonni at 4science.it) - */ - -public class OrcidAuthenticationFilter extends StatelessLoginFilter { - - public OrcidAuthenticationFilter(String url, AuthenticationManager authenticationManager, - RestAuthenticationService restAuthenticationService) { - super(url, authenticationManager, restAuthenticationService); - } - - @Override - public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) - throws AuthenticationException { - - req.setAttribute(ORCID_AUTH_ATTRIBUTE, ORCID_AUTH_ATTRIBUTE); - return authenticationManager.authenticate(new DSpaceAuthentication(null, null, emptyList())); - - } - - @Override - protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, - Authentication auth) throws IOException, ServletException { - - restAuthenticationService.addAuthenticationDataForUser(req, res, (DSpaceAuthentication) auth, true); - chain.doFilter(req, res); - - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index fb332cc8aa..d9be871c5d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -137,11 +137,8 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { .addFilterBefore(new ShibbolethLoginFilter("/api/authn/shibboleth", authenticationManager(), restAuthenticationService), LogoutFilter.class) - //Add a filter before our ORCID endpoints to do the authentication based on the data in the + //Add a filter before our OIDC endpoints to do the authentication based on the data in the // HTTP request - .addFilterBefore(new OrcidAuthenticationFilter("/api/authn/orcid", authenticationManager(), - restAuthenticationService), - LogoutFilter.class) .addFilterBefore(new OidcAuthenticationFilter("/api/authn/oidc", authenticationManager(), restAuthenticationService), LogoutFilter.class) From 99c8ad991fd5ddbfca9a0b9c1521ab1d058f026e Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Fri, 29 Oct 2021 15:40:23 -0500 Subject: [PATCH 0651/1254] Remove unusued DSpace-CRIS services in core-services.xml --- dspace/config/spring/api/core-services.xml | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 9ab2021ab2..ec8dda3491 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -134,26 +134,7 @@ - - - - - - - - - - - - - - - - - - - - + From b2a24301b949a0bfa36ba1aed69139a770ee73e5 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Tue, 23 Nov 2021 12:46:46 -0600 Subject: [PATCH 0652/1254] Disable DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES - Makes OIDC authentication sturdier: implementations can and do differ slightly, an unknown property should not prevent authentication --- .../org/dspace/authenticate/oidc/impl/OidcClientImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java index af482ec18d..78e91c3e05 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java @@ -15,6 +15,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import javax.annotation.PostConstruct; + +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; @@ -45,6 +48,11 @@ public class OidcClientImpl implements OidcClient { private final ObjectMapper objectMapper = new ObjectMapper(); + @PostConstruct + private void setup(){ + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + } + @Override public OidcTokenResponseDTO getAccessToken(String code) throws OidcClientException { List params = new ArrayList(); From 3c03a67c753f816974df3cef79ffe576a132fbcd Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Wed, 22 Dec 2021 17:10:34 -0600 Subject: [PATCH 0653/1254] Revise call to create new DSpaceAuthentication - Borrow the invocation of DSpaceAuthentication from the Shib plugin - Also borrow the comment explaining it from the Shib plugin --- .../org/dspace/app/rest/security/OidcAuthenticationFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java index 5cdd3b982a..300ea1e243 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java @@ -37,7 +37,8 @@ public class OidcAuthenticationFilter extends StatelessLoginFilter { public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { req.setAttribute(OIDC_AUTH_ATTRIBUTE, OIDC_AUTH_ATTRIBUTE); - return authenticationManager.authenticate(new DSpaceAuthentication(null, null, new ArrayList<>())); + // NOTE: because this authentication is implicit, we pass in an empty DSpaceAuthentication + return authenticationManager.authenticate(new DSpaceAuthentication()); } @Override From e8aa94696317e529564fea5fcc67a5600a37d3b6 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Wed, 22 Dec 2021 18:00:48 -0600 Subject: [PATCH 0654/1254] Iron out some checkstyle wrinkles --- .../java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java | 3 +-- .../org/dspace/app/rest/security/OidcAuthenticationFilter.java | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java index 78e91c3e05..ddab01e8cb 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/oidc/impl/OidcClientImpl.java @@ -14,7 +14,6 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; - import javax.annotation.PostConstruct; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -49,7 +48,7 @@ public class OidcClientImpl implements OidcClient { private final ObjectMapper objectMapper = new ObjectMapper(); @PostConstruct - private void setup(){ + private void setup() { objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java index 300ea1e243..79c2edcd9c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.security; import static org.dspace.authenticate.OidcAuthenticationBean.OIDC_AUTH_ATTRIBUTE; import java.io.IOException; -import java.util.ArrayList; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; From e6012b0ef77059f10d2f5e3b600852e3a0d85c9f Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Wed, 5 Jan 2022 14:46:12 -0600 Subject: [PATCH 0655/1254] Add missing isUsed override to OidcAuthentication code. --- .../org/dspace/authenticate/OidcAuthentication.java | 12 ++++++++++++ .../dspace/authenticate/OidcAuthenticationBean.java | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java index 354d69c4df..edaa87dd13 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthentication.java @@ -29,6 +29,8 @@ public class OidcAuthentication implements AuthenticationMethod { private final ServiceManager serviceManager = new DSpace().getServiceManager(); + private static final String OIDC_AUTHENTICATED = "oidc.authenticated"; + @Override public boolean canSelfRegister(Context context, HttpServletRequest request, String username) throws SQLException { return getOidcAuthentication().canSelfRegister(context, request, username); @@ -74,4 +76,14 @@ public class OidcAuthentication implements AuthenticationMethod { return serviceManager.getServiceByName("oidcAuthentication", OidcAuthenticationBean.class); } + @Override + public boolean isUsed(final Context context, final HttpServletRequest request) { + if (request != null && + context.getCurrentUser() != null && + request.getAttribute(OIDC_AUTHENTICATED) != null) { + return true; + } + return false; + } + } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java index a0b1fc8dd9..bfe83eea39 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java @@ -51,6 +51,8 @@ public class OidcAuthenticationBean implements AuthenticationMethod { private static final Logger LOGGER = LoggerFactory.getLogger(OidcAuthenticationBean.class); + private static final String OIDC_AUTHENTICATED = "oidc.authenticated"; + @Autowired private ConfigurationService configurationService; @@ -129,6 +131,7 @@ public class OidcAuthenticationBean implements AuthenticationMethod { EPerson ePerson = ePersonService.findByEmail(context, email); if (ePerson != null) { + request.setAttribute(OIDC_AUTHENTICATED, true); return ePerson.canLogIn() ? logInEPerson(context, ePerson) : BAD_ARGS; } @@ -256,4 +259,14 @@ public class OidcAuthenticationBean implements AuthenticationMethod { this.oidcClient = oidcClient; } + @Override + public boolean isUsed(final Context context, final HttpServletRequest request) { + if (request != null && + context.getCurrentUser() != null && + request.getAttribute(OIDC_AUTHENTICATED) != null) { + return true; + } + return false; + } + } From 3a04b92c808207c592c67b78127bb242fcd33017 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Fri, 21 Jan 2022 16:57:47 -0600 Subject: [PATCH 0656/1254] Requested changes - Add sane fall-back defaults for OIDC, where possible. - Improve error logging for missing properties - Include authentication-oidc.cfg in dspace.cfg - Add configuration examples for OIDC to local.cfg-EXAMPLE - Improve authentication-oidc.cfg with sane defaults and more comments --- .../authenticate/OidcAuthenticationBean.java | 30 +++++++++++++++---- dspace/config/dspace.cfg | 1 + dspace/config/local.cfg.EXAMPLE | 3 ++ dspace/config/modules/authentication-oidc.cfg | 28 ++++++++++------- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java index bfe83eea39..c2c72b76e3 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java @@ -16,8 +16,11 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import java.io.UnsupportedEncodingException; import java.sql.SQLException; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -147,11 +150,26 @@ public class OidcAuthenticationBean implements AuthenticationMethod { String redirectUri = configurationService.getProperty("authentication-oidc.redirect-url"); String tokenUrl = configurationService.getProperty("authentication-oidc.token-endpoint"); String userInfoUrl = configurationService.getProperty("authentication-oidc.user-info-endpoint"); - String scopes = String.join(" ", configurationService.getArrayProperty("authentication-oidc.scopes")); - String email = getEmailAttribute(); + String[] defaultScopes = + new String[] { + "openid", "email", "profile" + }; + String scopes = String.join(" ", configurationService.getArrayProperty("authentication-oidc.scopes", defaultScopes)); - if (isAnyBlank(authorizeUrl, clientId, redirectUri, scopes, clientSecret, tokenUrl, userInfoUrl, email)) { + if (isAnyBlank(authorizeUrl, clientId, redirectUri, clientSecret, tokenUrl, userInfoUrl)) { LOGGER.error("Missing mandatory configuration properties for OidcAuthenticationBean"); + + // prepare a Map of the properties which can not have sane defaults, but are still required + final Map map = Map.of("authorizeUrl", authorizeUrl, "clientId", clientId, "redirectUri", redirectUri, "clientSecret", clientSecret, "tokenUrl", tokenUrl, "userInfoUrl", userInfoUrl); + final Iterator> iterator = map.entrySet().iterator(); + + while (iterator.hasNext()) { + final Entry entry = iterator.next(); + + if (isBlank(entry.getValue())) { + LOGGER.error(" * {} is missing", entry.getKey()); + } + } return ""; } @@ -232,15 +250,15 @@ public class OidcAuthenticationBean implements AuthenticationMethod { } private String getEmailAttribute() { - return configurationService.getProperty("authentication-oidc.user-info.email"); + return configurationService.getProperty("authentication-oidc.user-info.email", "email"); } private String getFirstNameAttribute() { - return configurationService.getProperty("authentication-oidc.user-info.first-name"); + return configurationService.getProperty("authentication-oidc.user-info.first-name", "given_name"); } private String getLastNameAttribute() { - return configurationService.getProperty("authentication-oidc.user-info.last-name"); + return configurationService.getProperty("authentication-oidc.user-info.last-name", "family_name"); } private boolean canSelfRegister() { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index c261f0dd0b..6ab64dbed3 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1580,6 +1580,7 @@ include = ${module_dir}/altmetrics.cfg include = ${module_dir}/authentication.cfg include = ${module_dir}/authentication-ip.cfg include = ${module_dir}/authentication-ldap.cfg +include = ${module_dir}/authentication-oidc.cfg include = ${module_dir}/authentication-password.cfg include = ${module_dir}/authentication-shibboleth.cfg include = ${module_dir}/authentication-x509.cfg diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index f8a931cd25..50f5223861 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -190,6 +190,9 @@ db.schema = public # LDAP authentication/authorization. See authentication-ldap.cfg for default configuration. #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.LDAPAuthentication +# OIDC authentication/authorization. See authenication-oidc.cfg for default configuration. +#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OIDCAuthentication + # Shibboleth authentication/authorization. See authentication-shibboleth.cfg for default configuration. # Check also the cors settings below #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.ShibAuthentication diff --git a/dspace/config/modules/authentication-oidc.cfg b/dspace/config/modules/authentication-oidc.cfg index 55ce150e98..bbb8489a77 100644 --- a/dspace/config/modules/authentication-oidc.cfg +++ b/dspace/config/modules/authentication-oidc.cfg @@ -5,17 +5,20 @@ # Authentication plugin, when it is enabled. # #---------------------------------------------------------------# -# The domain of the OpenID Connect server -authentication-oidc.auth-server-domain = +# The Realm on the OIDC server we should use for authentication +authentication-oidc.auth-server-realm = + +# The Base URL of all OIDC server endpoints +authentication-oidc.auth-server-url = # The URL of the Token endpoint -authentication-oidc.token-endpoint = +authentication-oidc.token-endpoint = ${authentication-oidc.auth-server-url}/auth/realms/${authentication-oidc.auth-server-realm}/protocol/openid-connect/token # The URL of the Authorize endpoint -authentication-oidc.authorize-endpoint = +authentication-oidc.authorize-endpoint = ${authentication-oidc.auth-server-url}/auth/realms/${authentication-oidc.auth-server-realm}/protocol/openid-connect/auth # The URL of the Introspect endpoint -authentication-oidc.user-info-endpoint = +authentication-oidc.user-info-endpoint = ${authentication-oidc.auth-server-url}/auth/realms/${authentication-oidc.auth-server-realm}/protocol/openid-connect/userinfo # The registered client id authentication-oidc.client-id = @@ -27,16 +30,19 @@ authentication-oidc.client-secret = authentication-oidc.redirect-url = ${dspace.server.url}/api/authn/oidc # The scopes to request -authentication-oidc.scopes = +authentication-oidc.scopes = openid,email,profile -#Specify if the user can self register using OIDC (true|false). If not specified, true is assumed -authentication-oidc.can-self-register = +# Specify if the user can self register using OIDC (true|false). If not specified, true is assumed +# This should match the configuration of the OIDC server you are using. The default setting for +# Keycloak is true. Do set it to false if your OIDC server disallows self-registration. Otherwise, +# leave this set to true. +authentication-oidc.can-self-register = true #Specify the attribute present in the user info json related to the user's email -authentication-oidc.user-info.email = +authentication-oidc.user-info.email = email #Specify the attribute present in the user info json related to the user's first name -authentication-oidc.user-info.first-name = +authentication-oidc.user-info.first-name = given_name #Specify the attribute present in the user info json related to the user's last name -authentication-oidc.user-info.last-name = \ No newline at end of file +authentication-oidc.user-info.last-name = family_name \ No newline at end of file From 7c944d5eb5e8f56b75cc80cbd12ce5abacfa4737 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Fri, 21 Jan 2022 17:17:17 -0600 Subject: [PATCH 0657/1254] Requested change: Add logging for no such user and canSelfRegister=false --- .../java/org/dspace/authenticate/OidcAuthenticationBean.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java index c2c72b76e3..f8e6a479a4 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java @@ -138,6 +138,7 @@ public class OidcAuthenticationBean implements AuthenticationMethod { return ePerson.canLogIn() ? logInEPerson(context, ePerson) : BAD_ARGS; } + LOGGER.warn("Self registration is currently disabled for OIDC, and no ePerson could be found for email: {}", email); return canSelfRegister() ? registerNewEPerson(context, userInfo, email) : NO_SUCH_USER; } From e504597ee25155b27d522744cdcfb5b9e4aaaf5b Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Mon, 24 Jan 2022 12:35:44 -0600 Subject: [PATCH 0658/1254] Fix checkstyle errors --- .../authenticate/OidcAuthenticationBean.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java index f8e6a479a4..fb7b4a92af 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java @@ -20,7 +20,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -138,7 +137,8 @@ public class OidcAuthenticationBean implements AuthenticationMethod { return ePerson.canLogIn() ? logInEPerson(context, ePerson) : BAD_ARGS; } - LOGGER.warn("Self registration is currently disabled for OIDC, and no ePerson could be found for email: {}", email); + LOGGER.warn("Self registration is currently disabled for OIDC, and no ePerson could be found for email: {}", + email); return canSelfRegister() ? registerNewEPerson(context, userInfo, email) : NO_SUCH_USER; } @@ -155,19 +155,21 @@ public class OidcAuthenticationBean implements AuthenticationMethod { new String[] { "openid", "email", "profile" }; - String scopes = String.join(" ", configurationService.getArrayProperty("authentication-oidc.scopes", defaultScopes)); + String scopes = String.join(" ", configurationService.getArrayProperty("authentication-oidc.scopes", + defaultScopes)); if (isAnyBlank(authorizeUrl, clientId, redirectUri, clientSecret, tokenUrl, userInfoUrl)) { LOGGER.error("Missing mandatory configuration properties for OidcAuthenticationBean"); // prepare a Map of the properties which can not have sane defaults, but are still required - final Map map = Map.of("authorizeUrl", authorizeUrl, "clientId", clientId, "redirectUri", redirectUri, "clientSecret", clientSecret, "tokenUrl", tokenUrl, "userInfoUrl", userInfoUrl); + final Map map = Map.of("authorizeUrl", authorizeUrl, "clientId", clientId, "redirectUri", + redirectUri, "clientSecret", clientSecret, "tokenUrl", tokenUrl, "userInfoUrl", userInfoUrl); final Iterator> iterator = map.entrySet().iterator(); - + while (iterator.hasNext()) { final Entry entry = iterator.next(); - - if (isBlank(entry.getValue())) { + + if (isBlank(entry.getValue())) { LOGGER.error(" * {} is missing", entry.getKey()); } } From 0c1c2defcf2a8b04413dc16c3f61014e571651e1 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Mon, 24 Jan 2022 13:17:14 -0600 Subject: [PATCH 0659/1254] Add validation to OIDC redirect, based on suggestion from LGTMbot, code copied from Shibboleth, at the suggestion of @tdonohue --- .../dspace/app/rest/OidcRestController.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java index 7a94e527e3..166f2fddee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java @@ -8,12 +8,15 @@ package org.dspace.app.rest; import java.io.IOException; +import java.util.ArrayList; import java.util.List; + import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.AuthnRest; +import org.dspace.core.Utils; import org.dspace.services.ConfigurationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +55,26 @@ public class OidcRestController { if (StringUtils.isBlank(redirectUrl)) { redirectUrl = configurationService.getProperty("dspace.ui.url"); } - log.info("Redirecting to " + redirectUrl); - response.sendRedirect(redirectUrl); + + // Validate that the redirectURL matches either the server or UI hostname. It *cannot* be an arbitrary URL. + String redirectHostName = Utils.getHostName(redirectUrl); + String serverHostName = Utils.getHostName(configurationService.getProperty("dspace.server.url")); + ArrayList allowedHostNames = new ArrayList<>(); + allowedHostNames.add(serverHostName); + String[] allowedUrls = configurationService.getArrayProperty("rest.cors.allowed-origins"); + for (String url : allowedUrls) { + allowedHostNames.add(Utils.getHostName(url)); + } + + if (StringUtils.equalsAnyIgnoreCase(redirectHostName, allowedHostNames.toArray(new String[0]))) { + log.debug("OIDC redirecting to " + redirectUrl); + response.sendRedirect(redirectUrl); + } else { + log.error("Invalid OIDC redirectURL=" + redirectUrl + + ". URL doesn't match hostname of server or UI!"); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, + "Invalid redirectURL! Must match server or ui hostname."); + } + } } From 23e93ff2857b7099ba8005d0b5b6c3b65f052f1c Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Mon, 24 Jan 2022 13:19:22 -0600 Subject: [PATCH 0660/1254] Fix checkstyle error introduced by VScode's 'organize imports' suggestion --- .../src/main/java/org/dspace/app/rest/OidcRestController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java index 166f2fddee..e67d026d80 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java @@ -10,7 +10,6 @@ package org.dspace.app.rest; import java.io.IOException; import java.util.ArrayList; import java.util.List; - import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletResponse; From 6ede71866aa1d79eefe4349a9485c6e16fa7640f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 24 Jan 2022 16:54:14 -0800 Subject: [PATCH 0661/1254] Using updated constants in shared utils. --- .../app/itemimport/ItemImportServiceImpl.java | 30 ++++++++++++++----- .../IIIFCanvasDimensionServiceImpl.java | 17 ++++++----- .../org/dspace/iiif/util/IIIFSharedUtils.java | 8 +++-- .../app/iiif/service/utils/IIIFUtils.java | 14 ++++----- 4 files changed, 43 insertions(+), 26 deletions(-) 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 106e0e532a..6a6a70d574 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 @@ -7,6 +7,13 @@ */ package org.dspace.app.itemimport; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT_QUALIFIER; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE_ELEMENT; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_LABEL_ELEMENT; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_SCHEMA; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_TOC_ELEMENT; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH_QUALIFIER; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -1662,6 +1669,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea Bitstream bs = null; boolean notfound = true; + boolean updateRequired = false; + if (!isTest) { // find bitstream List bitstreams = itemService.getNonInternalBitstreams(c, myItem); @@ -1696,40 +1705,45 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea System.out.println("\tSetting description for " + bitstreamName); bs.setDescription(c, thisDescription); - bitstreamService.update(c, bs); + updateRequired = true; } if (labelExists) { MetadataField metadataField = metadataFieldService - .findByElement(c, "iiif", "label", null); + .findByElement(c, METADATA_IIIF_SCHEMA, METADATA_IIIF_LABEL_ELEMENT, null); System.out.println("\tSetting label to " + thisLabel + " in element " + metadataField.getElement() + " on " + bitstreamName); bitstreamService.addMetadata(c, bs, metadataField, null, thisLabel); - bitstreamService.update(c, bs); + updateRequired = true; } if (heightExists) { MetadataField metadataField = metadataFieldService - .findByElement(c, "iiif", "image", "height"); + .findByElement(c, METADATA_IIIF_SCHEMA, METADATA_IIIF_IMAGE_ELEMENT, + METADATA_IIIF_HEIGHT_QUALIFIER); System.out.println("\tSetting height to " + thisHeight + " in element " + metadataField.getElement() + " on " + bitstreamName); bitstreamService.addMetadata(c, bs, metadataField, null, thisHeight); - bitstreamService.update(c, bs); + updateRequired = true; } if (widthExists) { MetadataField metadataField = metadataFieldService - .findByElement(c, "iiif", "image", "width"); + .findByElement(c, METADATA_IIIF_SCHEMA, METADATA_IIIF_IMAGE_ELEMENT, + METADATA_IIIF_WIDTH_QUALIFIER); System.out.println("\tSetting width to " + thisWidth + " in element " + metadataField.getElement() + " on " + bitstreamName); bitstreamService.addMetadata(c, bs, metadataField, null, thisWidth); - bitstreamService.update(c, bs); + updateRequired = true; } if (tocExists) { MetadataField metadataField = metadataFieldService - .findByElement(c, "iiif", "toc", null); + .findByElement(c, METADATA_IIIF_SCHEMA, METADATA_IIIF_TOC_ELEMENT, null); System.out.println("\tSetting toc to " + thisToc + " in element " + metadataField.getElement() + " on " + bitstreamName); bitstreamService.addMetadata(c, bs, metadataField, null, thisToc); + updateRequired = true; + } + if (updateRequired) { bitstreamService.update(c, bs); } } diff --git a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java index d985786a04..ad36b65ab9 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/canvasdimension/IIIFCanvasDimensionServiceImpl.java @@ -7,10 +7,10 @@ */ package org.dspace.iiif.canvasdimension; -import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT; -import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT_QUALIFIER; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE_ELEMENT; import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_SCHEMA; -import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH_QUALIFIER; import java.io.IOException; import java.io.InputStream; @@ -62,7 +62,8 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic private int processed = 0; // used to check for existing canvas dimension - private static final String IIIF_WIDTH_METADATA = "iiif.image.width"; + private static final String IIIF_WIDTH_METADATA = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE_ELEMENT + + "." + METADATA_IIIF_WIDTH_QUALIFIER; @Override public void setForceProcessing(boolean force) { @@ -209,13 +210,13 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic */ private boolean setBitstreamMetadata(Context context, Bitstream bitstream, int[] dims) throws SQLException { dSpaceObjectService.clearMetadata(context, bitstream, METADATA_IIIF_SCHEMA, - METADATA_IIIF_IMAGE, METADATA_IIIF_WIDTH, Item.ANY); + METADATA_IIIF_IMAGE_ELEMENT, METADATA_IIIF_WIDTH_QUALIFIER, Item.ANY); dSpaceObjectService.setMetadataSingleValue(context, bitstream, METADATA_IIIF_SCHEMA, - METADATA_IIIF_IMAGE, METADATA_IIIF_WIDTH, null, String.valueOf(dims[0])); + METADATA_IIIF_IMAGE_ELEMENT, METADATA_IIIF_WIDTH_QUALIFIER, null, String.valueOf(dims[0])); dSpaceObjectService.clearMetadata(context, bitstream, METADATA_IIIF_SCHEMA, - METADATA_IIIF_IMAGE, METADATA_IIIF_HEIGHT, Item.ANY); + METADATA_IIIF_IMAGE_ELEMENT, METADATA_IIIF_HEIGHT_QUALIFIER, Item.ANY); dSpaceObjectService.setMetadataSingleValue(context, bitstream, METADATA_IIIF_SCHEMA, - METADATA_IIIF_IMAGE, METADATA_IIIF_HEIGHT, null, String.valueOf(dims[1])); + METADATA_IIIF_IMAGE_ELEMENT, METADATA_IIIF_HEIGHT_QUALIFIER, null, String.valueOf(dims[1])); if (!isQuiet) { System.out.println("Added IIIF canvas metadata to bitstream: " + bitstream.getID()); } diff --git a/dspace-api/src/main/java/org/dspace/iiif/util/IIIFSharedUtils.java b/dspace-api/src/main/java/org/dspace/iiif/util/IIIFSharedUtils.java index 21ddb8710e..ad0f974cb1 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/util/IIIFSharedUtils.java +++ b/dspace-api/src/main/java/org/dspace/iiif/util/IIIFSharedUtils.java @@ -36,9 +36,11 @@ public class IIIFSharedUtils { protected static final String IMAGE_SERVER_PATH = "iiif.image.server"; // IIIF metadata definitions public static final String METADATA_IIIF_SCHEMA = "iiif"; - public static final String METADATA_IIIF_IMAGE = "image"; - public static final String METADATA_IIIF_HEIGHT = "height"; - public static final String METADATA_IIIF_WIDTH = "width"; + public static final String METADATA_IIIF_IMAGE_ELEMENT = "image"; + public static final String METADATA_IIIF_TOC_ELEMENT = "toc"; + public static final String METADATA_IIIF_LABEL_ELEMENT = "label"; + public static final String METADATA_IIIF_HEIGHT_QUALIFIER = "height"; + public static final String METADATA_IIIF_WIDTH_QUALIFIER = "width"; protected static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); 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 8889cb5518..cce2b8c6fe 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 @@ -7,10 +7,10 @@ */ package org.dspace.app.iiif.service.utils; -import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT; -import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT_QUALIFIER; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE_ELEMENT; import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_SCHEMA; -import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH; +import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH_QUALIFIER; import java.sql.SQLException; import java.util.ArrayList; @@ -63,11 +63,11 @@ public class IIIFUtils { // metadata used to set the iiif viewing hint public static final String METADATA_IIIF_VIEWING_HINT = "iiif.viewing.hint"; // metadata used to set the width of the canvas that has not an explicit name - public static final String METADATA_IMAGE_WIDTH = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE - + "." + METADATA_IIIF_WIDTH; + public static final String METADATA_IMAGE_WIDTH = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE_ELEMENT + + "." + METADATA_IIIF_WIDTH_QUALIFIER; // metadata used to set the height of the canvas that has not an explicit name - public static final String METADATA_IMAGE_HEIGHT = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE - + "." + METADATA_IIIF_HEIGHT; + public static final String METADATA_IMAGE_HEIGHT = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE_ELEMENT + + "." + METADATA_IIIF_HEIGHT_QUALIFIER; // string used in the metadata toc as separator among the different levels public static final String TOC_SEPARATOR = "|||"; From b83291748adbaa8c1ca628693ad08dffe7c1e207 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 25 Jan 2022 17:48:49 +0100 Subject: [PATCH 0662/1254] fix inherit of policies in submission --- .../org/dspace/authorize/AuthorizeServiceImpl.java | 3 ++- .../java/org/dspace/content/ItemServiceImpl.java | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) 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 e1f29d519c..0420e5e691 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -508,9 +508,10 @@ public class AuthorizeServiceImpl implements AuthorizeService { List policies = getPolicies(c, src); //Only inherit non-ADMIN policies (since ADMIN policies are automatically inherited) + //and non-custom policies as these are manually applied when appropriate List nonAdminPolicies = new ArrayList<>(); for (ResourcePolicy rp : policies) { - if (rp.getAction() != Constants.ADMIN) { + if (rp.getAction() != Constants.ADMIN && !ResourcePolicy.TYPE_CUSTOM.contentEquals(rp.getRpType())) { nonAdminPolicies.add(rp); } } 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 b93d33a559..96dac1a4df 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -850,6 +850,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It List defaultCollectionPolicies = authorizeService .getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ); + List defaultItemPolicies = authorizeService.findPoliciesByDSOAndType(context, item, + ResourcePolicy.TYPE_CUSTOM); if (defaultCollectionPolicies.size() < 1) { throw new SQLException("Collection " + collection.getID() + " (" + collection.getHandle() + ")" @@ -864,12 +866,14 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // if come from InstallItem: remove all submission/workflow policies authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_SUBMISSION); authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_WORKFLOW); + addCustomPoliciesNotInPlace(context, mybundle, defaultItemPolicies); addDefaultPoliciesNotInPlace(context, mybundle, defaultCollectionPolicies); for (Bitstream bitstream : mybundle.getBitstreams()) { // if come from InstallItem: remove all submission/workflow policies authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_SUBMISSION); authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_WORKFLOW); + addCustomPoliciesNotInPlace(context, bitstream, defaultItemPolicies); addDefaultPoliciesNotInPlace(context, bitstream, defaultCollectionPolicies); } } @@ -1064,6 +1068,15 @@ prevent the generation of resource policy entry values with null dspace_object a } } + private void addCustomPoliciesNotInPlace(Context context, DSpaceObject dso, List customPolicies) + throws SQLException, AuthorizeException { + boolean customPoliciesAlreadyInPlace = authorizeService + .findPoliciesByDSOAndType(context, dso, ResourcePolicy.TYPE_CUSTOM).size() > 0; + if (!customPoliciesAlreadyInPlace) { + authorizeService.addPolicies(context, customPolicies, dso); + } + } + /** * Check whether or not there is already an RP on the given dso, which has actionId={@link Constants.READ} and * resourceTypeId={@link ResourcePolicy.TYPE_CUSTOM} From 34d9dbadd2c2b22e570cb44415919e9f6433f409 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 25 Jan 2022 18:15:09 +0100 Subject: [PATCH 0663/1254] minor fix --- .../main/java/org/dspace/authorize/AuthorizeServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0420e5e691..919e82f14f 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -511,7 +511,7 @@ public class AuthorizeServiceImpl implements AuthorizeService { //and non-custom policies as these are manually applied when appropriate List nonAdminPolicies = new ArrayList<>(); for (ResourcePolicy rp : policies) { - if (rp.getAction() != Constants.ADMIN && !ResourcePolicy.TYPE_CUSTOM.contentEquals(rp.getRpType())) { + if (rp.getAction() != Constants.ADMIN && !StringUtils.equals(rp.getRpType(), ResourcePolicy.TYPE_CUSTOM)) { nonAdminPolicies.add(rp); } } From abf198037a3c18191e74b818e562cebeef344626 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 25 Jan 2022 09:20:19 -0800 Subject: [PATCH 0664/1254] Shared image server query service --- .../dspace/iiif/IIIFApiQueryServiceImpl.java | 9 ----- .../app/iiif/service/utils/IIIFUtils.java | 37 +++---------------- dspace/config/spring/api/iiif-processing.xml | 2 +- 3 files changed, 6 insertions(+), 42 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 5d3a78b35d..04a08a7781 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java @@ -34,15 +34,6 @@ public class IIIFApiQueryServiceImpl implements IIIFApiQueryService { @Override public int[] getImageDimensions(Bitstream bitstream) { - return getIiifImageDimensions(bitstream); - } - - /** - * Retrieves image dimensions from the image server (IIIF Image API v.2.1.1). - * @param bitstream the bitstream DSO - * @return image dimensions - */ - private int[] getIiifImageDimensions(Bitstream bitstream) { int[] arr = new int[2]; String path = IIIFSharedUtils.getInfoJsonPath(bitstream); URL url; 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 39aba077d7..25b68fc4ec 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 @@ -12,11 +12,6 @@ import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE; import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_SCHEMA; import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -24,7 +19,6 @@ import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import de.digitalcollections.iiif.model.sharedcanvas.Resource; @@ -39,6 +33,7 @@ import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.service.BitstreamService; import org.dspace.core.Context; +import org.dspace.iiif.IIIFApiQueryService; import org.dspace.iiif.util.IIIFSharedUtils; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -92,6 +87,9 @@ public class IIIFUtils { @Autowired ConfigurationService configurationService; + @Autowired + IIIFApiQueryService iiifApiQueryService; + public List getIIIFBundles(Item item) { return IIIFSharedUtils.getIIIFBundles(item); } @@ -310,32 +308,7 @@ public class IIIFUtils { * @return image dimensions */ public int[] getImageDimensions(Bitstream bitstream) { - int[] arr = new int[2]; - String imageServer = configurationService.getProperty("iiif.image.server"); - String path = imageServer + bitstream.getID() + "/info.json"; - URL url; - try { - url = new URL(path); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("GET"); - int responseCode = con.getResponseCode(); - log.info("Retrieve dimensions from the IIIF image server response status: " + responseCode); - BufferedReader in = new BufferedReader( - new InputStreamReader(con.getInputStream())); - String inputLine; - StringBuilder response = new StringBuilder(); - while ((inputLine = in.readLine()) != null) { - response.append(inputLine); - } - in.close(); - JsonNode parent = new ObjectMapper().readTree(response.toString()); - arr[0] = parent.get("width").asInt(); - arr[1] = parent.get("height").asInt(); - return arr; - } catch (IOException e) { - e.printStackTrace(); - } - return null; + return iiifApiQueryService.getImageDimensions(bitstream); } /** diff --git a/dspace/config/spring/api/iiif-processing.xml b/dspace/config/spring/api/iiif-processing.xml index 21716516dd..3cc92637d3 100644 --- a/dspace/config/spring/api/iiif-processing.xml +++ b/dspace/config/spring/api/iiif-processing.xml @@ -5,6 +5,6 @@ - + From 346d041f7e09ae22d694af598b2e22e659e26aaf Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 25 Jan 2022 09:46:24 -0800 Subject: [PATCH 0665/1254] Bitstream cache. --- .../org/dspace/iiif/CacheEventConsumer.java | 166 ++++++++++++++++++ .../dspace/iiif/CanvasCacheEvictService.java | 22 +++ .../iiif/ManifestsCacheEvictService.java | 36 ++++ .../iiif/consumer/CacheEvictBeanLocator.java | 16 +- .../iiif/consumer/CacheEvictService.java | 8 +- .../iiif/consumer/IIIFCacheEventConsumer.java | 52 ++++-- .../app/iiif/service/utils/IIIFUtils.java | 2 + .../dspace/app/rest/cache/CacheLogger.java | 2 +- .../app/rest/cache/CanvasCacheLogger.java | 24 +++ .../src/main/resources/iiif/cache/ehcache.xml | 20 ++- dspace/config/dspace.cfg | 5 + 11 files changed, 329 insertions(+), 24 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/iiif/CacheEventConsumer.java create mode 100644 dspace-api/src/main/java/org/dspace/iiif/CanvasCacheEvictService.java create mode 100644 dspace-api/src/main/java/org/dspace/iiif/ManifestsCacheEvictService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CanvasCacheLogger.java diff --git a/dspace-api/src/main/java/org/dspace/iiif/CacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/CacheEventConsumer.java new file mode 100644 index 0000000000..5cfc89f1f8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/iiif/CacheEventConsumer.java @@ -0,0 +1,166 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.consumer; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.apache.logging.log4j.Logger; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.event.Consumer; +import org.dspace.event.Event; + + +/** + * This consumer is used to evict modified items from the manifests cache. + */ +public class CacheEventConsumer implements Consumer { + + private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(CacheEventConsumer.class); + + // When true all entries will be cleared from cache. + private boolean clearAll = false; + + // Collects modified items for individual removal from cache. + private final Set toEvictFromManifestCache = new HashSet<>(); + + // Collects modified bitstreams for individual removal from canvas dimension cache. + private final Set toEvictFromCanvasCache = new HashSet<>(); + + @Override + public void initialize() throws Exception { + } + + @Override + public void consume(Context ctx, Event event) throws Exception { + int st = event.getSubjectType(); + if (!(st == Constants.BUNDLE || st == Constants.ITEM || st == Constants.BITSTREAM)) { + return; + } + // This subject may become a reference to the parent Item that will be evicted from + // the manifests cache. + DSpaceObject subject = event.getSubject(ctx); + DSpaceObject unmodifiedSubject = event.getSubject(ctx); + + int et = event.getEventType(); + + if (et == Event.DELETE || et == Event.REMOVE) { + log.warn("IIIF event consumer cannot remove a single item from the cache when " + + "a bundle is deleted. The entire cache will be cleared."); + clearAll = true; + } + + if (st == Constants.BUNDLE) { + if ((et == Event.ADD || et == Event.MODIFY || et == Event.MODIFY_METADATA || et == Event.REMOVE + || et == Event.DELETE) && subject != null) { + // set subject to be the parent Item. + subject = ((Bundle) subject).getItems().get(0); + if (log.isDebugEnabled()) { + log.debug("Transforming Bundle event into Item event for " + + subject.getID()); + } + } else { + return; + } + } + + if (st == Constants.BITSTREAM) { + if (et == Event.DELETE || et == Event.REMOVE) { + log.warn("IIIF event consumer cannot remove a single item from the cache when " + + "a bitstream is deleted. The entire cache will be cleared."); + clearAll = true; + } + + if ((et == Event.ADD || et == Event.MODIFY_METADATA ) && subject != null + && ((Bitstream) subject).getBundles().size() > 0) { + // set subject to be the parent Item. + Bundle bundle = ((Bitstream) subject).getBundles().get(0); + subject = bundle.getItems().get(0); + if (log.isDebugEnabled()) { + log.debug("Transforming Bitstream event into Item event for " + + subject.getID()); + } + } else { + return; + } + } + + if (st == Constants.ITEM && et == Event.ADD) { + // nothing to evict from cache. + return; + } + + switch (et) { + case Event.ADD: + addToCacheEviction(subject, unmodifiedSubject, st); + break; + case Event.MODIFY: + addToCacheEviction(subject, unmodifiedSubject, st); + break; + case Event.MODIFY_METADATA: + addToCacheEviction(subject, unmodifiedSubject, st); + break; + case Event.REMOVE: + addToCacheEviction(subject, unmodifiedSubject, st); + break; + case Event.DELETE: + addToCacheEviction(subject, unmodifiedSubject, st); + break; + default: { + log.warn("ManifestsCacheEventConsumer should not have been given this kind of " + + "subject in an event, skipping: " + event); + } + } + } + + private void addToCacheEviction(DSpaceObject subject, DSpaceObject subject2, int type) { + if (type == Constants.BITSTREAM) { + toEvictFromCanvasCache.add(subject2); + } + toEvictFromManifestCache.add(subject); + } + + @Override + public void end(Context ctx) throws Exception { + // Get the eviction service beans. + ManifestsCacheEvictService manifestsCacheEvictService = CacheEvictBeanLocator.getManifestsCacheEvictService(); + CanvasCacheEvictService canvasCacheEvictService = CacheEvictBeanLocator.getCanvasCacheEvictService(); + + if (manifestsCacheEvictService != null) { + if (clearAll) { + manifestsCacheEvictService.evictAllCacheValues(); + } else { + for (DSpaceObject dso : toEvictFromManifestCache) { + UUID uuid = dso.getID(); + manifestsCacheEvictService.evictSingleCacheValue(uuid.toString()); + } + } + } + if (canvasCacheEvictService != null) { + for (DSpaceObject dso : toEvictFromCanvasCache) { + UUID uuid = dso.getID(); + canvasCacheEvictService.evictSingleCacheValue(uuid.toString()); + } + } + + clearAll = false; + toEvictFromManifestCache.clear(); + toEvictFromCanvasCache.clear(); + } + + @Override + public void finish(Context ctx) throws Exception { + + } + +} diff --git a/dspace-api/src/main/java/org/dspace/iiif/CanvasCacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/CanvasCacheEvictService.java new file mode 100644 index 0000000000..11dffeaa8e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/iiif/CanvasCacheEvictService.java @@ -0,0 +1,22 @@ +package org.dspace.iiif; + +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; + +@Component +public class CanvasCacheEvictService { + + // The cache that is managed by this service. + static final String CACHE_NAME = "canvasdimensions"; + + @Autowired + CacheManager cacheManager; + + public void evictSingleCacheValue(String cacheKey) { + Objects.requireNonNull(cacheManager.getCache(CACHE_NAME)).evict(cacheKey); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/iiif/ManifestsCacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/ManifestsCacheEvictService.java new file mode 100644 index 0000000000..967d0667a6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/iiif/ManifestsCacheEvictService.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.iiif.consumer; + +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; + +/** + * Removes items from the iiif manifests cache. + */ +@Component +public class ManifestsCacheEvictService { + + // The cache that is managed by this service. + static final String CACHE_NAME = "manifests"; + + @Autowired + CacheManager cacheManager; + + public void evictSingleCacheValue(String cacheKey) { + Objects.requireNonNull(cacheManager.getCache(CACHE_NAME)).evict(cacheKey); + } + + public void evictAllCacheValues() { + Objects.requireNonNull(cacheManager.getCache(CACHE_NAME)).clear(); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictBeanLocator.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictBeanLocator.java index 0e55e99d16..43e6adc20f 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictBeanLocator.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictBeanLocator.java @@ -13,14 +13,15 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** - * Exposes the Spring web application's IIIF cache evict service to the DSpace event consumer. + * Exposes the Spring application's IIIF cache evict service to the DSpace event consumer. */ @Component public class CacheEvictBeanLocator implements ApplicationContextAware { private static ApplicationContext context; - private static final String CACHE_SERVICE = "cacheEvictService"; + private static final String MANIFESTS_CACHE_EVICT_SERVICE = "manifestsCacheEvictService"; + private static final String CANVAS_DIMENSIONS_EVICT_SERVICE = "canvasCacheEvictService"; @Override public void setApplicationContext(ApplicationContext appContext) @@ -32,9 +33,16 @@ public class CacheEvictBeanLocator implements ApplicationContextAware { return context; } - public static CacheEvictService getCacheEvictService() { + public static ManifestsCacheEvictService getManifestsCacheEvictService() { if (context != null) { - return (CacheEvictService) context.getBean(CACHE_SERVICE); + return (ManifestsCacheEvictService) context.getBean(MANIFESTS_CACHE_EVICT_SERVICE); + } + return null; + } + + public static CanvasCacheEvictService getCanvasCacheEvictService() { + if (context != null) { + return (CanvasCacheEvictService) context.getBean(CANVAS_DIMENSIONS_EVICT_SERVICE); } return null; } diff --git a/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java index fa4a61ac79..967d0667a6 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java @@ -7,6 +7,8 @@ */ package org.dspace.iiif.consumer; +import java.util.Objects; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Component; @@ -15,7 +17,7 @@ import org.springframework.stereotype.Component; * Removes items from the iiif manifests cache. */ @Component -public class CacheEvictService { +public class ManifestsCacheEvictService { // The cache that is managed by this service. static final String CACHE_NAME = "manifests"; @@ -24,11 +26,11 @@ public class CacheEvictService { CacheManager cacheManager; public void evictSingleCacheValue(String cacheKey) { - cacheManager.getCache(CACHE_NAME).evict(cacheKey); + Objects.requireNonNull(cacheManager.getCache(CACHE_NAME)).evict(cacheKey); } public void evictAllCacheValues() { - cacheManager.getCache(CACHE_NAME).clear(); + Objects.requireNonNull(cacheManager.getCache(CACHE_NAME)).clear(); } } diff --git a/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java index f165d4785c..5cfc89f1f8 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java @@ -24,9 +24,9 @@ import org.dspace.event.Event; /** * This consumer is used to evict modified items from the manifests cache. */ -public class IIIFCacheEventConsumer implements Consumer { +public class CacheEventConsumer implements Consumer { - private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFCacheEventConsumer.class); + private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(CacheEventConsumer.class); // When true all entries will be cleared from cache. private boolean clearAll = false; @@ -34,6 +34,9 @@ public class IIIFCacheEventConsumer implements Consumer { // Collects modified items for individual removal from cache. private final Set toEvictFromManifestCache = new HashSet<>(); + // Collects modified bitstreams for individual removal from canvas dimension cache. + private final Set toEvictFromCanvasCache = new HashSet<>(); + @Override public void initialize() throws Exception { } @@ -44,7 +47,11 @@ public class IIIFCacheEventConsumer implements Consumer { if (!(st == Constants.BUNDLE || st == Constants.ITEM || st == Constants.BITSTREAM)) { return; } + // This subject may become a reference to the parent Item that will be evicted from + // the manifests cache. DSpaceObject subject = event.getSubject(ctx); + DSpaceObject unmodifiedSubject = event.getSubject(ctx); + int et = event.getEventType(); if (et == Event.DELETE || et == Event.REMOVE) { @@ -95,43 +102,60 @@ public class IIIFCacheEventConsumer implements Consumer { switch (et) { case Event.ADD: - toEvictFromManifestCache.add(subject); + addToCacheEviction(subject, unmodifiedSubject, st); break; case Event.MODIFY: - toEvictFromManifestCache.add(subject); + addToCacheEviction(subject, unmodifiedSubject, st); break; case Event.MODIFY_METADATA: - toEvictFromManifestCache.add(subject); + addToCacheEviction(subject, unmodifiedSubject, st); break; case Event.REMOVE: - toEvictFromManifestCache.add(subject); + addToCacheEviction(subject, unmodifiedSubject, st); break; case Event.DELETE: - toEvictFromManifestCache.add(subject); + addToCacheEviction(subject, unmodifiedSubject, st); break; default: { - log.warn("IIIFCacheEventConsumer should not have been given this kind of " - + "subject in an event, skipping: " + event.toString()); + log.warn("ManifestsCacheEventConsumer should not have been given this kind of " + + "subject in an event, skipping: " + event); } } } + private void addToCacheEviction(DSpaceObject subject, DSpaceObject subject2, int type) { + if (type == Constants.BITSTREAM) { + toEvictFromCanvasCache.add(subject2); + } + toEvictFromManifestCache.add(subject); + } + @Override public void end(Context ctx) throws Exception { - // Gets the service bean. - CacheEvictService cacheEvictService = CacheEvictBeanLocator.getCacheEvictService(); - if (cacheEvictService != null) { + // Get the eviction service beans. + ManifestsCacheEvictService manifestsCacheEvictService = CacheEvictBeanLocator.getManifestsCacheEvictService(); + CanvasCacheEvictService canvasCacheEvictService = CacheEvictBeanLocator.getCanvasCacheEvictService(); + + if (manifestsCacheEvictService != null) { if (clearAll) { - cacheEvictService.evictAllCacheValues(); + manifestsCacheEvictService.evictAllCacheValues(); } else { for (DSpaceObject dso : toEvictFromManifestCache) { UUID uuid = dso.getID(); - cacheEvictService.evictSingleCacheValue(uuid.toString()); + manifestsCacheEvictService.evictSingleCacheValue(uuid.toString()); } } } + if (canvasCacheEvictService != null) { + for (DSpaceObject dso : toEvictFromCanvasCache) { + UUID uuid = dso.getID(); + canvasCacheEvictService.evictSingleCacheValue(uuid.toString()); + } + } + clearAll = false; toEvictFromManifestCache.clear(); + toEvictFromCanvasCache.clear(); } @Override 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 25b68fc4ec..82720329c6 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 @@ -37,6 +37,7 @@ import org.dspace.iiif.IIIFApiQueryService; import org.dspace.iiif.util.IIIFSharedUtils; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; @Component @@ -307,6 +308,7 @@ public class IIIFUtils { * @param bitstream the bitstream DSO * @return image dimensions */ + @Cacheable(key = "#bitstream.getID().toString()", cacheNames = "canvasdimensions") public int[] getImageDimensions(Bitstream bitstream) { return iiifApiQueryService.getImageDimensions(bitstream); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CacheLogger.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CacheLogger.java index 18b26b3ceb..bd77c578e6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CacheLogger.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CacheLogger.java @@ -18,7 +18,7 @@ public class CacheLogger implements CacheEventListener { @Override public void onEvent(CacheEvent cacheEvent) { - log.info("Cache Event Type: {} | Manifest Key: {} ", + log.info("Cache Event Type: {} | Key: {} ", cacheEvent.getType(), cacheEvent.getKey()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CanvasCacheLogger.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CanvasCacheLogger.java new file mode 100644 index 0000000000..eaa08000ee --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/cache/CanvasCacheLogger.java @@ -0,0 +1,24 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.cache; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.ehcache.event.CacheEvent; +import org.ehcache.event.CacheEventListener; + +public class CanvasCacheLogger implements CacheEventListener { + private static final Logger log = LogManager.getLogger(CacheLogger.class); + + @Override + public void onEvent(CacheEvent cacheEvent) { + log.info("Canvas Dimension Cache Event Type: {} | Key: {} ", + cacheEvent.getType(), cacheEvent.getKey()); + } + +} diff --git a/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml index cedbff2d46..3a55d3b691 100644 --- a/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml +++ b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml @@ -29,6 +29,22 @@ 10 - - + + + + org.dspace.app.rest.cache.CanvasCacheLogger + ASYNCHRONOUS + UNORDERED + CREATED + EXPIRED + EVICTED + + + + 3000 + 4 + + + + diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 03865bafc3..a3d3651811 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -749,8 +749,13 @@ event.consumer.authority.class = org.dspace.authority.indexer.AuthorityConsumer event.consumer.authority.filters = Item+Modify|Modify_Metadata # iiif consumer +<<<<<<< Updated upstream event.consumer.iiif.class = org.dspace.iiif.consumer.IIIFCacheEventConsumer event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+Remove:Bundle+ALL:Bitstream+All +======= +event.consumer.iiif.class = org.dspace.iiif.CacheEventConsumer +event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+Remove:Bundle+ALL:Bitstream+Modify_Metadata:Bitstream+Delete:Bitstream+Modify +>>>>>>> Stashed changes # ...set to true to enable testConsumer messages to standard output #testConsumer.verbose = true From fbe1b55eddbb1c9af9e1670dab72262b2e2e9d64 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 25 Jan 2022 10:33:58 -0800 Subject: [PATCH 0666/1254] Updated cache logging. --- .../org/dspace/iiif/CacheEventConsumer.java | 166 ------------------ .../iiif/consumer/CacheEvictService.java | 36 ---- .../CanvasCacheEvictService.java | 2 +- .../iiif/consumer/IIIFCacheEventConsumer.java | 4 +- .../ManifestsCacheEvictService.java | 0 .../src/main/resources/iiif/cache/ehcache.xml | 1 + 6 files changed, 4 insertions(+), 205 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/iiif/CacheEventConsumer.java delete mode 100644 dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java rename dspace-api/src/main/java/org/dspace/iiif/{ => consumer}/CanvasCacheEvictService.java (94%) rename dspace-api/src/main/java/org/dspace/iiif/{ => consumer}/ManifestsCacheEvictService.java (100%) diff --git a/dspace-api/src/main/java/org/dspace/iiif/CacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/CacheEventConsumer.java deleted file mode 100644 index 5cfc89f1f8..0000000000 --- a/dspace-api/src/main/java/org/dspace/iiif/CacheEventConsumer.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.iiif.consumer; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -import org.apache.logging.log4j.Logger; -import org.dspace.content.Bitstream; -import org.dspace.content.Bundle; -import org.dspace.content.DSpaceObject; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.event.Consumer; -import org.dspace.event.Event; - - -/** - * This consumer is used to evict modified items from the manifests cache. - */ -public class CacheEventConsumer implements Consumer { - - private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(CacheEventConsumer.class); - - // When true all entries will be cleared from cache. - private boolean clearAll = false; - - // Collects modified items for individual removal from cache. - private final Set toEvictFromManifestCache = new HashSet<>(); - - // Collects modified bitstreams for individual removal from canvas dimension cache. - private final Set toEvictFromCanvasCache = new HashSet<>(); - - @Override - public void initialize() throws Exception { - } - - @Override - public void consume(Context ctx, Event event) throws Exception { - int st = event.getSubjectType(); - if (!(st == Constants.BUNDLE || st == Constants.ITEM || st == Constants.BITSTREAM)) { - return; - } - // This subject may become a reference to the parent Item that will be evicted from - // the manifests cache. - DSpaceObject subject = event.getSubject(ctx); - DSpaceObject unmodifiedSubject = event.getSubject(ctx); - - int et = event.getEventType(); - - if (et == Event.DELETE || et == Event.REMOVE) { - log.warn("IIIF event consumer cannot remove a single item from the cache when " + - "a bundle is deleted. The entire cache will be cleared."); - clearAll = true; - } - - if (st == Constants.BUNDLE) { - if ((et == Event.ADD || et == Event.MODIFY || et == Event.MODIFY_METADATA || et == Event.REMOVE - || et == Event.DELETE) && subject != null) { - // set subject to be the parent Item. - subject = ((Bundle) subject).getItems().get(0); - if (log.isDebugEnabled()) { - log.debug("Transforming Bundle event into Item event for " - + subject.getID()); - } - } else { - return; - } - } - - if (st == Constants.BITSTREAM) { - if (et == Event.DELETE || et == Event.REMOVE) { - log.warn("IIIF event consumer cannot remove a single item from the cache when " + - "a bitstream is deleted. The entire cache will be cleared."); - clearAll = true; - } - - if ((et == Event.ADD || et == Event.MODIFY_METADATA ) && subject != null - && ((Bitstream) subject).getBundles().size() > 0) { - // set subject to be the parent Item. - Bundle bundle = ((Bitstream) subject).getBundles().get(0); - subject = bundle.getItems().get(0); - if (log.isDebugEnabled()) { - log.debug("Transforming Bitstream event into Item event for " - + subject.getID()); - } - } else { - return; - } - } - - if (st == Constants.ITEM && et == Event.ADD) { - // nothing to evict from cache. - return; - } - - switch (et) { - case Event.ADD: - addToCacheEviction(subject, unmodifiedSubject, st); - break; - case Event.MODIFY: - addToCacheEviction(subject, unmodifiedSubject, st); - break; - case Event.MODIFY_METADATA: - addToCacheEviction(subject, unmodifiedSubject, st); - break; - case Event.REMOVE: - addToCacheEviction(subject, unmodifiedSubject, st); - break; - case Event.DELETE: - addToCacheEviction(subject, unmodifiedSubject, st); - break; - default: { - log.warn("ManifestsCacheEventConsumer should not have been given this kind of " - + "subject in an event, skipping: " + event); - } - } - } - - private void addToCacheEviction(DSpaceObject subject, DSpaceObject subject2, int type) { - if (type == Constants.BITSTREAM) { - toEvictFromCanvasCache.add(subject2); - } - toEvictFromManifestCache.add(subject); - } - - @Override - public void end(Context ctx) throws Exception { - // Get the eviction service beans. - ManifestsCacheEvictService manifestsCacheEvictService = CacheEvictBeanLocator.getManifestsCacheEvictService(); - CanvasCacheEvictService canvasCacheEvictService = CacheEvictBeanLocator.getCanvasCacheEvictService(); - - if (manifestsCacheEvictService != null) { - if (clearAll) { - manifestsCacheEvictService.evictAllCacheValues(); - } else { - for (DSpaceObject dso : toEvictFromManifestCache) { - UUID uuid = dso.getID(); - manifestsCacheEvictService.evictSingleCacheValue(uuid.toString()); - } - } - } - if (canvasCacheEvictService != null) { - for (DSpaceObject dso : toEvictFromCanvasCache) { - UUID uuid = dso.getID(); - canvasCacheEvictService.evictSingleCacheValue(uuid.toString()); - } - } - - clearAll = false; - toEvictFromManifestCache.clear(); - toEvictFromCanvasCache.clear(); - } - - @Override - public void finish(Context ctx) throws Exception { - - } - -} diff --git a/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java deleted file mode 100644 index 967d0667a6..0000000000 --- a/dspace-api/src/main/java/org/dspace/iiif/consumer/CacheEvictService.java +++ /dev/null @@ -1,36 +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.iiif.consumer; - -import java.util.Objects; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.CacheManager; -import org.springframework.stereotype.Component; - -/** - * Removes items from the iiif manifests cache. - */ -@Component -public class ManifestsCacheEvictService { - - // The cache that is managed by this service. - static final String CACHE_NAME = "manifests"; - - @Autowired - CacheManager cacheManager; - - public void evictSingleCacheValue(String cacheKey) { - Objects.requireNonNull(cacheManager.getCache(CACHE_NAME)).evict(cacheKey); - } - - public void evictAllCacheValues() { - Objects.requireNonNull(cacheManager.getCache(CACHE_NAME)).clear(); - } - -} diff --git a/dspace-api/src/main/java/org/dspace/iiif/CanvasCacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/CanvasCacheEvictService.java similarity index 94% rename from dspace-api/src/main/java/org/dspace/iiif/CanvasCacheEvictService.java rename to dspace-api/src/main/java/org/dspace/iiif/consumer/CanvasCacheEvictService.java index 11dffeaa8e..bf3b840394 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/CanvasCacheEvictService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/CanvasCacheEvictService.java @@ -1,4 +1,4 @@ -package org.dspace.iiif; +package org.dspace.iiif.consumer; import java.util.Objects; diff --git a/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java index 5cfc89f1f8..1d6a678301 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java @@ -24,9 +24,9 @@ import org.dspace.event.Event; /** * This consumer is used to evict modified items from the manifests cache. */ -public class CacheEventConsumer implements Consumer { +public class IIIFCacheEventConsumer implements Consumer { - private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(CacheEventConsumer.class); + private final static Logger log = org.apache.logging.log4j.LogManager.getLogger(IIIFCacheEventConsumer.class); // When true all entries will be cleared from cache. private boolean clearAll = false; diff --git a/dspace-api/src/main/java/org/dspace/iiif/ManifestsCacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/ManifestsCacheEvictService.java similarity index 100% rename from dspace-api/src/main/java/org/dspace/iiif/ManifestsCacheEvictService.java rename to dspace-api/src/main/java/org/dspace/iiif/consumer/ManifestsCacheEvictService.java diff --git a/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml index 3a55d3b691..3155ea58ec 100644 --- a/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml +++ b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml @@ -21,6 +21,7 @@ UNORDERED CREATED EXPIRED + REMOVED EVICTED From 0579b7d99c8560845eaf31b501201a72fb8b0729 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Tue, 25 Jan 2022 13:21:59 -0800 Subject: [PATCH 0667/1254] Added config for updating default dimension for all images w/o metadata. --- .../consumer/CanvasCacheEvictService.java | 7 ++++ .../app/iiif/service/CanvasService.java | 3 +- .../app/iiif/service/utils/IIIFUtils.java | 36 +++++++++++++++++-- dspace/config/dspace.cfg | 5 --- dspace/config/modules/iiif.cfg | 10 ++++-- 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/iiif/consumer/CanvasCacheEvictService.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/CanvasCacheEvictService.java index bf3b840394..56cd432d91 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/consumer/CanvasCacheEvictService.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/CanvasCacheEvictService.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.iiif.consumer; import java.util.Objects; 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 3f9aba7dde..77ed229f1b 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 @@ -56,6 +56,7 @@ public class CanvasService extends AbstractResourceService { protected String[] BITSTREAM_METADATA_FIELDS; + /** * Constructor. * @@ -70,7 +71,7 @@ public class CanvasService extends AbstractResourceService { * Checks for bitstream height and width metadata in the first * bitstream in first IIIF bundle. If the bitstream metadata is not * found, uses the IIIF image service to update the default canvas - * dimensions for this request. + * dimensions for this request. Called once for each manifest. * @param bundles IIIF bundles for this item */ protected void setCanvasDimensions(List bundles) { 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 82720329c6..64fb8abb5b 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 @@ -91,6 +91,7 @@ public class IIIFUtils { @Autowired IIIFApiQueryService iiifApiQueryService; + public List getIIIFBundles(Item item) { return IIIFSharedUtils.getIIIFBundles(item); } @@ -310,7 +311,7 @@ public class IIIFUtils { */ @Cacheable(key = "#bitstream.getID().toString()", cacheNames = "canvasdimensions") public int[] getImageDimensions(Bitstream bitstream) { - return iiifApiQueryService.getImageDimensions(bitstream); + return iiifApiQueryService.getImageDimensions(bitstream); } /** @@ -367,6 +368,9 @@ public class IIIFUtils { * @return the width in pixel for the canvas associated with the bitstream */ public int getCanvasWidth(Bitstream bitstream, Bundle bundle, Item item, int defaultWidth) { + if (defaultWidth == -1) { + defaultWidth = setDefaultSize(bitstream, METADATA_IMAGE_WIDTH, defaultWidth); + } return getSizeFromMetadata(bitstream, METADATA_IMAGE_WIDTH, getSizeFromMetadata(bundle, METADATA_IMAGE_WIDTH, getSizeFromMetadata(item, METADATA_IMAGE_WIDTH, defaultWidth))); @@ -385,6 +389,9 @@ public class IIIFUtils { * @return the height in pixel for the canvas associated with the bitstream */ public int getCanvasHeight(Bitstream bitstream, Bundle bundle, Item item, int defaultHeight) { + if (defaultHeight == -1) { + defaultHeight = setDefaultSize(bitstream, METADATA_IMAGE_HEIGHT, defaultHeight); + } return getSizeFromMetadata(bitstream, METADATA_IMAGE_HEIGHT, getSizeFromMetadata(bundle, METADATA_IMAGE_HEIGHT, getSizeFromMetadata(item, METADATA_IMAGE_HEIGHT, defaultHeight))); @@ -403,7 +410,32 @@ public class IIIFUtils { private int getSizeFromMetadata(DSpaceObject dso, String metadata, int defaultValue) { return dso.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(metadata)) - .findFirst().map(m -> castToInt(m, defaultValue)).orElse(defaultValue); + .findFirst().map(m -> castToInt(m, defaultValue)) + .orElse(defaultValue); + } + + /** + * Updates the default value for image width or height. The dimension is updated + * when the iiif default configuration is set to -1. The default dimension is + * updated only when the bitstream lacks the dimension metadata. + * @param bitstream bistream + * @param metadata iiif metadata field + * @param defaultValue current default dimension + * @return default dimension + */ + private int setDefaultSize(Bitstream bitstream, String metadata, int defaultValue) { + if (bitstream.getMetadata().stream().noneMatch(m -> m.getMetadataField().toString('.') + .contentEquals(metadata))) { + if (metadata.contentEquals(METADATA_IMAGE_WIDTH)) { + int[] dims = getImageDimensions(bitstream); + return dims[0]; + } + if (metadata.contentEquals(METADATA_IMAGE_HEIGHT)) { + int[] dims = getImageDimensions(bitstream); + return dims[1]; + } + } + return defaultValue; } /** diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a3d3651811..03865bafc3 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -749,13 +749,8 @@ event.consumer.authority.class = org.dspace.authority.indexer.AuthorityConsumer event.consumer.authority.filters = Item+Modify|Modify_Metadata # iiif consumer -<<<<<<< Updated upstream event.consumer.iiif.class = org.dspace.iiif.consumer.IIIFCacheEventConsumer event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+Remove:Bundle+ALL:Bitstream+All -======= -event.consumer.iiif.class = org.dspace.iiif.CacheEventConsumer -event.consumer.iiif.filters = Item+Modify:Item+Modify_Metadata:Item+Delete:Item+Remove:Bundle+ALL:Bitstream+Modify_Metadata:Bitstream+Delete:Bitstream+Modify ->>>>>>> Stashed changes # ...set to true to enable testConsumer messages to standard output #testConsumer.verbose = true diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index d6820b5305..5430478302 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -77,6 +77,10 @@ iiif.logo.image = ${dspace.ui.url}/assets/images/dspace-logo.svg iiif.document.viewing.hint = individuals # default value for the canvas size. Can be overridden at the item, bundle or bitstream level -# via the iiif.image.width e iiif.image.height metadata -# iiif.canvas.default-width = 1200 -# iiif.canvas.default-height = 1600 +# via the iiif.image.width and iiif.image.height metadata. +# If you want DSpace to retrieve accurate default dimensions for all images that lack height and width metadata, +# set both values to be -1. These lookups can be expensive, so it's always best to update your bitstream +# metadata with accurate iiif height and width dimensions for each image as soon as possible. +# iiif.canvas.default-width = 2400 +# iiif.canvas.default-height = 3600 + From b0ce61d3fe933613af499beb591e1b0ee19d2639 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Tue, 25 Jan 2022 17:00:50 -0600 Subject: [PATCH 0668/1254] Rename OidcAuthenticationFilter to OidcLoginFilter, as per request --- .../{OidcAuthenticationFilter.java => OidcLoginFilter.java} | 4 ++-- .../dspace/app/rest/security/WebSecurityConfiguration.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/security/{OidcAuthenticationFilter.java => OidcLoginFilter.java} (91%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java similarity index 91% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java index 79c2edcd9c..c84840e770 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcAuthenticationFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OidcLoginFilter.java @@ -25,9 +25,9 @@ import org.springframework.security.core.AuthenticationException; * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) */ -public class OidcAuthenticationFilter extends StatelessLoginFilter { +public class OidcLoginFilter extends StatelessLoginFilter { - public OidcAuthenticationFilter(String url, AuthenticationManager authenticationManager, + public OidcLoginFilter(String url, AuthenticationManager authenticationManager, RestAuthenticationService restAuthenticationService) { super(url, authenticationManager, restAuthenticationService); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index d9be871c5d..38e8c1d7f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -139,7 +139,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { LogoutFilter.class) //Add a filter before our OIDC endpoints to do the authentication based on the data in the // HTTP request - .addFilterBefore(new OidcAuthenticationFilter("/api/authn/oidc", authenticationManager(), + .addFilterBefore(new OidcLoginFilter("/api/authn/oidc", authenticationManager(), restAuthenticationService), LogoutFilter.class) // Add a custom Token based authentication filter based on the token previously given to the client From 42cae867dbe901131fa3e90b1ea7093ad9cbc002 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Wed, 26 Jan 2022 16:02:49 +0100 Subject: [PATCH 0669/1254] Move the citation generation in a single method again --- .../java/org/dspace/curate/CitationPage.java | 2 +- .../CitationDocumentServiceImpl.java | 32 +++++++++---------- .../service/CitationDocumentService.java | 9 ++---- .../app/rest/BitstreamRestController.java | 6 +++- .../app/rest/utils/BitstreamResource.java | 2 +- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CitationPage.java b/dspace-api/src/main/java/org/dspace/curate/CitationPage.java index 0e6d4992b1..5d1dca5243 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CitationPage.java +++ b/dspace-api/src/main/java/org/dspace/curate/CitationPage.java @@ -154,7 +154,7 @@ public class CitationPage extends AbstractCurationTask { try { //Create the cited document InputStream citedInputStream = - citationDocument.getCitedDocument(Curator.curationContext(), bitstream); + citationDocument.makeCitedDocument(Curator.curationContext(), bitstream).getLeft(); //Add the cited document to the approiate bundle this.addCitedPageToItem(citedInputStream, bundle, pBundle, dBundle, displayMap, item, bitstream); diff --git a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java index e81066071c..d51a3dfc7f 100644 --- a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.pdfbox.pdmodel.PDDocument; @@ -296,33 +297,30 @@ public class CitationDocumentServiceImpl implements CitationDocumentService, Ini } @Override - public InputStream getCitedDocument(Context context, Bitstream bitstream) - throws SQLException, AuthorizeException, IOException { - byte [] citedDocumentContent = getCitedDocumentContent(context, bitstream); - return new ByteArrayInputStream(citedDocumentContent); - } - - @Override - public Long getCitedDocumentLength(Context context, Bitstream bitstream) - throws SQLException, AuthorizeException, IOException { - byte[] citedDocumentContent = getCitedDocumentContent(context, bitstream); - return Long.valueOf(citedDocumentContent.length); - } - - private byte[] getCitedDocumentContent(Context context, Bitstream bitstream) - throws SQLException, AuthorizeException, IOException { + public Pair makeCitedDocument(Context context, Bitstream bitstream) + throws IOException, SQLException, AuthorizeException { PDDocument document = new PDDocument(); PDDocument sourceDocument = new PDDocument(); try { Item item = (Item) bitstreamService.getParentObject(context, bitstream); - sourceDocument = sourceDocument.load(bitstreamService.retrieve(context, bitstream)); + final InputStream inputStream = bitstreamService.retrieve(context, bitstream); + try { + sourceDocument = sourceDocument.load(inputStream); + } finally { + inputStream.close(); + } PDPage coverPage = new PDPage(citationPageFormat); generateCoverPage(context, document, coverPage, item); addCoverPageToDocument(document, sourceDocument, coverPage); + + //We already have the full PDF in memory, so keep it there try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { document.save(out); - return out.toByteArray(); + + byte[] data = out.toByteArray(); + return Pair.of(new ByteArrayInputStream(data), Long.valueOf(data.length)); } + } finally { sourceDocument.close(); document.close(); diff --git a/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java b/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java index a116ccf0b1..4a59de3f5f 100644 --- a/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java +++ b/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDFont; @@ -83,12 +84,8 @@ public interface CitationDocumentService { * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public InputStream getCitedDocument(Context context, Bitstream bitstream) - throws SQLException, AuthorizeException, IOException; - - public Long getCitedDocumentLength(Context context, Bitstream bitstream) - throws SQLException, AuthorizeException, IOException; - + public Pair makeCitedDocument(Context context, Bitstream bitstream) + throws IOException, SQLException, AuthorizeException; /** * @param page page diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index ab227682b4..75d3c9886c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -12,6 +12,7 @@ import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFI import static org.springframework.web.bind.annotation.RequestMethod.PUT; import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import java.util.List; import java.util.UUID; @@ -21,6 +22,7 @@ import javax.ws.rs.core.Response; import org.apache.catalina.connector.ClientAbortException; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -133,7 +135,9 @@ public class BitstreamRestController { try { long filesize; if (citationDocumentService.isCitationEnabledForBitstream(bit, context)) { - filesize = citationDocumentService.getCitedDocumentLength(context, bit); + final Pair citedDocument = citationDocumentService.makeCitedDocument(context, bit); + filesize = citedDocument.getRight(); + citedDocument.getLeft().close(); } else { filesize = bit.getSizeBytes(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java index 57ea2bcffa..5b8c54629c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java @@ -66,7 +66,7 @@ public class BitstreamResource extends AbstractResource { InputStream out; if (citationDocumentService.isCitationEnabledForBitstream(bitstream, context)) { - out = citationDocumentService.getCitedDocument(context, bitstream); + out = citationDocumentService.makeCitedDocument(context, bitstream).getLeft(); } else { out = bitstreamService.retrieve(context, bitstream); } From 87a338b0fdec2f1ac2d7c523852823687d67b4d6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 26 Jan 2022 17:27:07 +0100 Subject: [PATCH 0670/1254] added test to verify correct inheritance of access condition --- .../rest/WorkspaceItemRestRepositoryIT.java | 162 ++++++++++++++++++ .../rest/matcher/ResourcePolicyMatcher.java | 2 +- 2 files changed, 163 insertions(+), 1 deletion(-) 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 acb0e25b1b..0f38c3a9e5 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 @@ -68,6 +68,7 @@ import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.builder.ResourcePolicyBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; @@ -7085,4 +7086,165 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isForbidden()); } + @Test + public void verifyCorrectInheritanceOfAccessConditionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Group anonymousGroup = EPersonServiceFactory.getInstance().getGroupService() + .findByName(context, Group.ANONYMOUS); + Group adminGroup = EPersonServiceFactory.getInstance().getGroupService() + .findByName(context, Group.ADMIN); + + Community community = CommunityBuilder.createCommunity(context) + .withName("Com") + .build(); + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Col") + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + InputStream xml = getClass().getResourceAsStream("pubmed-test.xml"); + + final MockMultipartFile xmlFile = new MockMultipartFile("file", "/local/path/pubmed-test.xml", + "application/xml", xml); + + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("My Article") + .withIssueDate("2019-03-16") + .withFulltext("upload.pdf", "/local/path/simple-article.pdf", pdf) + .build(); + + Bundle bundle = wItem.getItem().getBundles().get(0); + Bitstream bitstream = bundle.getBitstreams().get(0); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + List addAccessCondition = new ArrayList(); + List> accessConditions = new ArrayList>(); + + Map accessCondition1 = new HashMap(); + accessCondition1.put("name", "administrator"); + accessConditions.add(accessCondition1); + + Map accessCondition2 = new HashMap(); + accessCondition2.put("name", "embargo"); + accessCondition2.put("startDate", "2022-01-31T01:00:00Z"); + accessConditions.add(accessCondition2); + + addAccessCondition.add(new AddOperation("/sections/defaultAC/accessConditions", + accessConditions)); + + String patchBody = getPatchContent(addAccessCondition); + // add access conditions + getClient(tokenEPerson).perform(patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(tokenEPerson).perform(get("/api/submission/workspaceitems/" + wItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("embargo"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()) + .andExpect(jsonPath("$.sections.upload.files[0].accessConditions", empty())) + .andExpect(jsonPath("$.sections.upload.files[1]").doesNotExist()); + + // upload second file + getClient(tokenEPerson).perform(fileUpload("/api/submission/workspaceitems/" + wItem.getID()) + .file(xmlFile)) + .andExpect(status().isCreated()); + + // prepare patch body with accessCondition for second file + List ops = new ArrayList<>(); + Map accessCondition = new HashMap<>(); + accessCondition.put("name", "embargo"); + accessCondition.put("startDate", "2022-03-22T01:00:00Z"); + ops.add(new AddOperation("/sections/upload/files/1/accessConditions/-", accessCondition)); + patchBody = getPatchContent(ops); + + // submit patch + getClient(tokenEPerson).perform(patch("/api/submission/workspaceitems/" + wItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + // verify if the access conditions have been applied correctly + getClient(tokenEPerson).perform(get("/api/submission/workspaceitems/" + wItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", + is("administrator"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name", + is("embargo"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].startDate", + is("2022-01-31"))) + .andExpect(jsonPath("$.sections.defaultAC.accessConditions[2].name").doesNotExist()) + .andExpect(jsonPath("$.sections.upload.files[0].accessConditions", empty())) + .andExpect(jsonPath("$.sections.upload.files[1].accessConditions[0].name", + is("embargo"))) + .andExpect(jsonPath("$.sections.upload.files[1].accessConditions[0].startDate", + is("2022-03-22"))); + + // submit the workspaceitem to complete the deposit + getClient(tokenAdmin).perform(post("/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // verify bundle policies + getClient(tokenAdmin).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bundle.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.containsInAnyOrder( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, + bundle, ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator"), + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, + bundle, ResourcePolicy.TYPE_CUSTOM, Constants.READ, "embargo") + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // verify bitstream policies + getClient(tokenAdmin).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.containsInAnyOrder( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, + bitstream, ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator"), + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, + bitstream, ResourcePolicy.TYPE_CUSTOM, Constants.READ, "embargo") + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + // verify item policies + getClient(tokenAdmin).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", wItem.getItem().getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.containsInAnyOrder( + ResourcePolicyMatcher.matchResourcePolicyProperties(adminGroup, null, + wItem.getItem(), ResourcePolicy.TYPE_CUSTOM, Constants.READ, "administrator"), + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, + wItem.getItem(), ResourcePolicy.TYPE_CUSTOM, Constants.READ, "embargo") + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + bundle = context.reloadEntity(bundle); + Bitstream bitstream2 = bundle.getBitstreams().get(1); + + // verify bitstream2 policies + getClient(tokenAdmin).perform(get("/api/authz/resourcepolicies/search/resource") + .param("uuid", bitstream2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.resourcepolicies", Matchers.contains( + ResourcePolicyMatcher.matchResourcePolicyProperties(anonymousGroup, null, + bitstream2, ResourcePolicy.TYPE_CUSTOM, Constants.READ, "embargo") + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java index b0423b5455..d80fb638cd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ResourcePolicyMatcher.java @@ -43,7 +43,7 @@ public class ResourcePolicyMatcher { hasJsonPath("$.policyType", is(rpType)) : hasNoJsonPath("$.policyType"), hasJsonPath("$.type", is("resourcepolicy")), - hasJsonPath("$._embedded.resource.id", is(dso.getID().toString())), + hasJsonPath("$._embedded.resource.uuid", is(dso.getID().toString())), eperson != null ? hasJsonPath("$._embedded.eperson.id", is(eperson.getID().toString())) : From ecf24b54f19582175885fdea67997b7d5e8740ba Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 26 Jan 2022 14:35:08 -0800 Subject: [PATCH 0671/1254] Fixed dimension cache. --- .../org/dspace/iiif/util/IIIFSharedUtils.java | 1 + .../app/iiif/service/CanvasService.java | 79 +++++++++++++++++-- .../app/iiif/service/ManifestService.java | 16 +++- .../app/iiif/service/utils/IIIFUtils.java | 30 ------- 4 files changed, 86 insertions(+), 40 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/iiif/util/IIIFSharedUtils.java b/dspace-api/src/main/java/org/dspace/iiif/util/IIIFSharedUtils.java index 21ddb8710e..90aeee9ffe 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/util/IIIFSharedUtils.java +++ b/dspace-api/src/main/java/org/dspace/iiif/util/IIIFSharedUtils.java @@ -43,6 +43,7 @@ public class IIIFSharedUtils { protected static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private IIIFSharedUtils() {} public static boolean isIIIFItem(Item item) { 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 77ed229f1b..189e4d6f62 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 @@ -7,6 +7,8 @@ */ package org.dspace.app.iiif.service; +import static org.dspace.app.iiif.service.utils.IIIFUtils.METADATA_IMAGE_WIDTH; + import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -56,6 +58,12 @@ public class CanvasService extends AbstractResourceService { protected String[] BITSTREAM_METADATA_FIELDS; + /** + * Used when default dimensions are set to -1 in configuration. + */ + int dynamicDefaultWidth = 0; + int dynamicDefaultHeight = 0; + /** * Constructor. @@ -65,16 +73,18 @@ public class CanvasService extends AbstractResourceService { public CanvasService(ConfigurationService configurationService) { setConfiguration(configurationService); BITSTREAM_METADATA_FIELDS = configurationService.getArrayProperty("iiif.metadata.bitstream"); + // Set default dimensions in parent class. + setDefaultCanvasDimensions(); } /** - * Checks for bitstream height and width metadata in the first - * bitstream in first IIIF bundle. If the bitstream metadata is not - * found, uses the IIIF image service to update the default canvas + * 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. * @param bundles IIIF bundles for this item */ - protected void setCanvasDimensions(List bundles) { + protected void guessCanvasDimensions(List bundles) { Bitstream firstBistream = bundles.get(0).getBitstreams().get(0); if (!utils.hasWidthMetadata(firstBistream)) { int[] imageDims = utils.getImageDimensions(firstBistream); @@ -83,8 +93,59 @@ public class CanvasService extends AbstractResourceService { defaultCanvasWidthFallback = imageDims[0]; defaultCanvasHeightFallback = imageDims[1]; } + setDefaultCanvasDimensions(); } - 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. + * @param bitstream + */ + private void setCanvasDimensions(Bitstream bitstream) { + if (DEFAULT_CANVAS_HEIGHT == -1 && DEFAULT_CANVAS_WIDTH == -1) { + // When the default dimension is -1, update default dimensions when the + // image has no width metadata. + if (bitstream.getMetadata().stream().noneMatch(m -> m.getMetadataField().toString('.') + .contentEquals(METADATA_IMAGE_WIDTH))) { + int[] imageDims = utils.getImageDimensions(bitstream); + if (imageDims != null && imageDims.length == 2) { + // update the dynamic default dimensions for this bitstream + dynamicDefaultWidth = imageDims[0]; + dynamicDefaultHeight = imageDims[1]; + } + if (imageDims == null) { + // use fallback. + dynamicDefaultWidth = defaultCanvasWidthFallback; + dynamicDefaultHeight = defaultCanvasHeightFallback; + log.error("Unable to retrieve dimensions from the image server for: " + bitstream.getID() + + " Using default dimensions."); + } + } + } + } + + /** + * Use the dynamic default if the configured default width is -1. + * @return + */ + private int getDefaultWidth() { + if (DEFAULT_CANVAS_WIDTH == -1) { + return dynamicDefaultWidth; + } + return DEFAULT_CANVAS_WIDTH; + } + + /** + * Use the dynamic default if the configured default height is -1. + * @return + */ + private int getDefaultHeight() { + if (DEFAULT_CANVAS_HEIGHT == -1) { + return dynamicDefaultHeight; + } + return DEFAULT_CANVAS_HEIGHT; } /** @@ -105,10 +166,12 @@ public class CanvasService extends AbstractResourceService { String canvasNaming = utils.getCanvasNaming(item, I18nUtil.getMessage("iiif.canvas.default-naming")); String label = utils.getIIIFLabel(bitstream, canvasNaming + " " + pagePosition); - int canvasWidth = utils.getCanvasWidth(bitstream, bundle, item, DEFAULT_CANVAS_WIDTH); - int canvasHeight = utils.getCanvasHeight(bitstream, bundle, item, DEFAULT_CANVAS_HEIGHT); - UUID bitstreamId = bitstream.getID(); + setCanvasDimensions(bitstream); + + int canvasWidth = utils.getCanvasWidth(bitstream, bundle, item, getDefaultWidth()); + int canvasHeight = utils.getCanvasHeight(bitstream, bundle, item, getDefaultHeight()); + UUID bitstreamId = bitstream.getID(); ImageContentGenerator image = imageContentService.getImageContent(bitstreamId, mimeType, imageUtil.getImageProfile(), IMAGE_PATH); 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 c846df6ca5..a9611593d9 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 @@ -85,6 +85,12 @@ public class ManifestService extends AbstractResourceService { MetadataExposureService metadataExposureService; protected String[] METADATA_FIELDS; + + /** + * Estimate image dimension metadata. + */ + boolean guessCanvasDimension; + /** * Constructor. * @param configurationService the DSpace configuration service. @@ -102,7 +108,10 @@ public class ManifestService extends AbstractResourceService { * @return manifest as JSON */ public String getManifest(Item item, Context context) { - + // If default dimensions are provided via configuration do not guess the default dimension. + String wid = configurationService.getProperty("iiif.canvas.default-width"); + String hgt = configurationService.getProperty("iiif.canvas.default-height"); + guessCanvasDimension = (wid == null && hgt == null); populateManifest(item, context); return utils.asJson(manifestGenerator.generateResource()); } @@ -146,7 +155,10 @@ public class ManifestService extends AbstractResourceService { // Get bundles that contain manifest data. List bundles = utils.getIIIFBundles(item); // Set the default canvas dimensions. - canvasService.setCanvasDimensions(bundles); + if (guessCanvasDimension) { + canvasService.guessCanvasDimensions(bundles); + } + // canvasService.setDefaultCanvasDimensions(); for (Bundle bnd : bundles) { String bundleToCPrefix = null; if (bundles.size() > 1) { 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 64fb8abb5b..dc046e39b8 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 @@ -368,9 +368,6 @@ public class IIIFUtils { * @return the width in pixel for the canvas associated with the bitstream */ public int getCanvasWidth(Bitstream bitstream, Bundle bundle, Item item, int defaultWidth) { - if (defaultWidth == -1) { - defaultWidth = setDefaultSize(bitstream, METADATA_IMAGE_WIDTH, defaultWidth); - } return getSizeFromMetadata(bitstream, METADATA_IMAGE_WIDTH, getSizeFromMetadata(bundle, METADATA_IMAGE_WIDTH, getSizeFromMetadata(item, METADATA_IMAGE_WIDTH, defaultWidth))); @@ -389,9 +386,6 @@ public class IIIFUtils { * @return the height in pixel for the canvas associated with the bitstream */ public int getCanvasHeight(Bitstream bitstream, Bundle bundle, Item item, int defaultHeight) { - if (defaultHeight == -1) { - defaultHeight = setDefaultSize(bitstream, METADATA_IMAGE_HEIGHT, defaultHeight); - } return getSizeFromMetadata(bitstream, METADATA_IMAGE_HEIGHT, getSizeFromMetadata(bundle, METADATA_IMAGE_HEIGHT, getSizeFromMetadata(item, METADATA_IMAGE_HEIGHT, defaultHeight))); @@ -414,30 +408,6 @@ public class IIIFUtils { .orElse(defaultValue); } - /** - * Updates the default value for image width or height. The dimension is updated - * when the iiif default configuration is set to -1. The default dimension is - * updated only when the bitstream lacks the dimension metadata. - * @param bitstream bistream - * @param metadata iiif metadata field - * @param defaultValue current default dimension - * @return default dimension - */ - private int setDefaultSize(Bitstream bitstream, String metadata, int defaultValue) { - if (bitstream.getMetadata().stream().noneMatch(m -> m.getMetadataField().toString('.') - .contentEquals(metadata))) { - if (metadata.contentEquals(METADATA_IMAGE_WIDTH)) { - int[] dims = getImageDimensions(bitstream); - return dims[0]; - } - if (metadata.contentEquals(METADATA_IMAGE_HEIGHT)) { - int[] dims = getImageDimensions(bitstream); - return dims[1]; - } - } - return defaultValue; - } - /** * Utility method to cast a metadata value to int. The defaultInt is returned if * the metadata value is not a valid integer From ddbaabcbb532eb69b97b839cc8efb88ce6922619 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Thu, 27 Jan 2022 10:57:54 -0600 Subject: [PATCH 0672/1254] Requested changes - only warn about failure to find a matching ePerson if canSelfRegister is false - tell LGTMbot everything is fine with the redirect - remove comments about nonexistent ORCID login code --- .../org/dspace/authenticate/OidcAuthenticationBean.java | 8 ++++++-- .../main/java/org/dspace/app/rest/OidcRestController.java | 2 +- dspace/config/modules/authentication.cfg | 6 ------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java index fb7b4a92af..41b40066b3 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/OidcAuthenticationBean.java @@ -137,8 +137,12 @@ public class OidcAuthenticationBean implements AuthenticationMethod { return ePerson.canLogIn() ? logInEPerson(context, ePerson) : BAD_ARGS; } - LOGGER.warn("Self registration is currently disabled for OIDC, and no ePerson could be found for email: {}", - email); + // if self registration is disabled, warn about this failure to find a matching eperson + if (! canSelfRegister()) { + LOGGER.warn("Self registration is currently disabled for OIDC, and no ePerson could be found for email: {}", + email); + } + return canSelfRegister() ? registerNewEPerson(context, userInfo, email) : NO_SUCH_USER; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java index e67d026d80..ab34a72a8d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java @@ -67,7 +67,7 @@ public class OidcRestController { if (StringUtils.equalsAnyIgnoreCase(redirectHostName, allowedHostNames.toArray(new String[0]))) { log.debug("OIDC redirecting to " + redirectUrl); - response.sendRedirect(redirectUrl); + response.sendRedirect(redirectUrl); // lgtm [java/unvalidated-url-redirection] } else { log.error("Invalid OIDC redirectURL=" + redirectUrl + ". URL doesn't match hostname of server or UI!"); diff --git a/dspace/config/modules/authentication.cfg b/dspace/config/modules/authentication.cfg index 7cc6a13b35..df07f91533 100644 --- a/dspace/config/modules/authentication.cfg +++ b/dspace/config/modules/authentication.cfg @@ -24,9 +24,6 @@ # * X.509 Certificate Authentication # Plugin class: org.dspace.authenticate.X509Authentication # Configuration file: authentication-x509.cfg -# * ORCID Authentication -# Plugin class: org.dspace.authenticate.OrcidAuthentication -# Configuration file: authentication-orcid.cfg # * OIDC Authentication # Plugin class: org.dspace.authenticate.OidcAuthentication # Configuration file: authentication-oidc.cfg @@ -52,9 +49,6 @@ # X.509 certificate authentication. See authentication-x509.cfg for default configuration. #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.X509Authentication -# ORCID authentication. See authentication-orcid.cfg for default configuration. -#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OrcidAuthentication - # OIDC authentication. See authentication-oidc.cfg for default configuration. #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OidcAuthentication From 19aa7ca53e9ba2575aeb8421c41543eefe0f18d2 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 27 Jan 2022 20:35:15 -0800 Subject: [PATCH 0673/1254] Added REMOVED event to canvas cache logger config. --- dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml index 3155ea58ec..cc9ad164f7 100644 --- a/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml +++ b/dspace-server-webapp/src/main/resources/iiif/cache/ehcache.xml @@ -38,6 +38,7 @@ UNORDERED CREATED EXPIRED + REMOVED EVICTED From 530d69bad88ecd51156bc33877d394545595b451 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Jan 2022 09:47:02 -0500 Subject: [PATCH 0674/1254] Switch Hibernate to JCache #8099 --- dspace-api/pom.xml | 15 +++++++++++++-- dspace/config/hibernate.cfg.xml | 11 +++++++---- dspace/config/spring/api/core-hibernate.xml | 5 +++-- pom.xml | 3 ++- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 635163e990..f703f63424 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -1,4 +1,6 @@ - + 4.0.0 org.dspace dspace-api @@ -335,9 +337,13 @@ + + org.hibernate + hibernate-core + org.hibernate - hibernate-ehcache + hibernate-jcache @@ -346,6 +352,11 @@ + + org.ehcache + ehcache + ${ehcache.version} + org.hibernate hibernate-jpamodelgen diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 39f5a11378..de8d7add00 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -21,12 +21,15 @@ true true - org.hibernate.cache.ehcache.EhCacheRegionFactory true + + + ENABLE_SELECTIVE - - - diff --git a/dspace/config/spring/api/core-hibernate.xml b/dspace/config/spring/api/core-hibernate.xml index 76d7b5b1ad..45f7d15dc5 100644 --- a/dspace/config/spring/api/core-hibernate.xml +++ b/dspace/config/spring/api/core-hibernate.xml @@ -1,7 +1,8 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" + default-lazy-init="true"> @@ -17,7 +18,7 @@ ${db.dialect} ${db.schema} - + file:${dspace.dir}/config/hibernate-ehcache-config.xml diff --git a/pom.xml b/pom.xml index 6e7b2e1ec0..7d0d9b058a 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 8.8.1 1.2.22 + 3.1.3 2.3.4 2.12.3 @@ -1131,7 +1132,7 @@ org.hibernate - hibernate-ehcache + hibernate-jcache ${hibernate.version} From 1599aea88601f66a3548b8a381f33c654fba59ca Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 28 Jan 2022 09:48:57 -0500 Subject: [PATCH 0675/1254] Begin moving CachingService to Ehcache v3 # 8099 --- dspace-services/pom.xml | 7 +++---- .../dspace/services/caching/CachingServiceImpl.java | 10 +++++----- .../dspace/services/caching/model/EhcacheCache.java | 6 +++--- .../org/dspace/services/caching/EhcacheCacheTest.java | 4 ++-- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index e09253a75c..2fdb60a1c4 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -90,11 +90,10 @@ org.apache.commons commons-lang3 - - net.sf.ehcache - ehcache-core - 2.6.11 + org.ehcache + ehcache + ${ehcache.version} compile diff --git a/dspace-services/src/main/java/org/dspace/services/caching/CachingServiceImpl.java b/dspace-services/src/main/java/org/dspace/services/caching/CachingServiceImpl.java index ab24cb40fa..cd803b4df1 100644 --- a/dspace-services/src/main/java/org/dspace/services/caching/CachingServiceImpl.java +++ b/dspace-services/src/main/java/org/dspace/services/caching/CachingServiceImpl.java @@ -19,8 +19,6 @@ import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import net.sf.ehcache.Ehcache; -import net.sf.ehcache.Statistics; import org.dspace.kernel.ServiceManager; import org.dspace.kernel.mixins.ConfigChangeListener; import org.dspace.services.CachingService; @@ -32,6 +30,8 @@ import org.dspace.services.model.Cache; import org.dspace.services.model.CacheConfig; import org.dspace.services.model.CacheConfig.CacheScope; import org.dspace.services.model.RequestInterceptor; +import org.ehcache.core.Ehcache; +import org.ehcache.Statistics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -120,14 +120,14 @@ public final class CachingServiceImpl /** * The underlying cache manager; injected. */ - protected net.sf.ehcache.CacheManager cacheManager; + protected org.ehcache.CacheManager cacheManager; @Autowired(required = true) - public void setCacheManager(net.sf.ehcache.CacheManager cacheManager) { + public void setCacheManager(org.ehcache.CacheManager cacheManager) { this.cacheManager = cacheManager; } - public net.sf.ehcache.CacheManager getCacheManager() { + public org.ehcache.CacheManager getCacheManager() { return cacheManager; } diff --git a/dspace-services/src/main/java/org/dspace/services/caching/model/EhcacheCache.java b/dspace-services/src/main/java/org/dspace/services/caching/model/EhcacheCache.java index a3e86748ac..5f9b16791c 100644 --- a/dspace-services/src/main/java/org/dspace/services/caching/model/EhcacheCache.java +++ b/dspace-services/src/main/java/org/dspace/services/caching/model/EhcacheCache.java @@ -11,12 +11,12 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import net.sf.ehcache.Ehcache; -import net.sf.ehcache.Element; -import net.sf.ehcache.Status; import org.dspace.services.model.Cache; import org.dspace.services.model.CacheConfig; import org.dspace.services.model.CacheConfig.CacheScope; +import org.ehcache.core.Ehcache; +import org.ehcache.Element; +import org.ehcache.Status; /** diff --git a/dspace-services/src/test/java/org/dspace/services/caching/EhcacheCacheTest.java b/dspace-services/src/test/java/org/dspace/services/caching/EhcacheCacheTest.java index 271d39b6ce..a7328f5f27 100644 --- a/dspace-services/src/test/java/org/dspace/services/caching/EhcacheCacheTest.java +++ b/dspace-services/src/test/java/org/dspace/services/caching/EhcacheCacheTest.java @@ -14,13 +14,13 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import net.sf.ehcache.CacheManager; -import net.sf.ehcache.Ehcache; import org.dspace.services.caching.model.EhcacheCache; import org.dspace.services.model.Cache; import org.dspace.services.model.CacheConfig; import org.dspace.services.model.CacheConfig.CacheScope; import org.dspace.test.DSpaceAbstractKernelTest; +import org.ehcache.CacheManager; +import org.ehcache.core.Ehcache; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; From d74b85e9010fbf64d4f1f54055bacd2b76aaf075 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 28 Jan 2022 18:15:34 +0100 Subject: [PATCH 0676/1254] added java doc --- .../dspace/content/service/ItemService.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) 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 167b9dbb79..d5e2f67767 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 @@ -472,10 +472,40 @@ public interface ItemService public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection) throws java.sql.SQLException, AuthorizeException; + /** + * Adjust the Bundle and Bitstream policies to reflect what have been defined + * during the submission/workflow. The temporary SUBMISSION and WORKFLOW + * policies are removed and the policies defined at the item and collection + * level are copied and inherited as appropriate. Custom selected Item policies + * are copied to the bundle/bitstream only if no explicit custom policies were + * already applied to the bundle/bitstream. Collection's policies are inherited + * if there are no other policies defined or if the append mode is defined by + * the configuration via the core.authorization.installitem.inheritance-read.append-mode property + * + * @param context DSpace context object + * @param item Item to adjust policies on + * @param collection Collection + * @throws SQLException If database error + * @throws AuthorizeException If authorization error + */ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException; + /** + * Adjust the Item's policies to reflect what have been defined during the + * submission/workflow. The temporary SUBMISSION and WORKFLOW policies are + * removed and the default policies defined at the collection level are + * inherited as appropriate. Collection's policies are inherited if there are no + * other policies defined or if the append mode is defined by the configuration + * via the core.authorization.installitem.inheritance-read.append-mode property + * + * @param context DSpace context object + * @param item Item to adjust policies on + * @param collection Collection + * @throws SQLException If database error + * @throws AuthorizeException If authorization error + */ public void adjustItemPolicies(Context context, Item item, Collection collection) throws SQLException, AuthorizeException; From 14a961b3868811b96109c016bd18ef5d3c6fdf89 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Fri, 28 Jan 2022 11:24:47 -0600 Subject: [PATCH 0677/1254] Fix example OIDC line in local.cfg.EXAMPLE, wrong case! --- dspace/config/local.cfg.EXAMPLE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 50f5223861..bdedcfd05e 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -191,7 +191,7 @@ db.schema = public #plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.LDAPAuthentication # OIDC authentication/authorization. See authenication-oidc.cfg for default configuration. -#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OIDCAuthentication +#plugin.sequence.org.dspace.authenticate.AuthenticationMethod = org.dspace.authenticate.OidcAuthentication # Shibboleth authentication/authorization. See authentication-shibboleth.cfg for default configuration. # Check also the cors settings below From e593e90dfb564ef3a963d8afb203b30626346a36 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Fri, 28 Jan 2022 12:20:13 -0600 Subject: [PATCH 0678/1254] 8134 - Fix the example requestiem helpdesk strategy example - Add autowire-candidate='true' to the example in requestitem config, as per suggestion from @bbranan. fixes #8134 --- dspace/config/spring/api/requestitem.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace/config/spring/api/requestitem.xml b/dspace/config/spring/api/requestitem.xml index 1288753c85..cc18c7916f 100644 --- a/dspace/config/spring/api/requestitem.xml +++ b/dspace/config/spring/api/requestitem.xml @@ -26,6 +26,7 @@ + id="org.dspace.app.requestitem.RequestItemAuthorExtractor" + autowire-candidate='true'>--> From 372ee07b2ff266cdca3bab1ef2d649d05af36ef9 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Fri, 28 Jan 2022 13:00:32 -0600 Subject: [PATCH 0679/1254] 8138 Fix SQL/HQL in itemDAOImpl.java --- .../src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java index fd404e2f3e..c4125696a8 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemDAOImpl.java @@ -392,7 +392,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO implements ItemDA public Iterator findByLastModifiedSince(Context context, Date since) throws SQLException { Query query = createQuery(context, - "SELECT i FROM item i WHERE last_modified > :last_modified ORDER BY id"); + "SELECT i FROM Item i WHERE last_modified > :last_modified ORDER BY id"); query.setParameter("last_modified", since, TemporalType.TIMESTAMP); return iterate(query); } From 294045d7602fc8fa7231dfc240bf2fa83a632feb Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 28 Jan 2022 20:17:24 +0100 Subject: [PATCH 0680/1254] refactored AccessConditionConfigurationService --- .../AccessConditionConfigurationService.java | 20 ++++++++----------- .../config/spring/api/access-conditions.xml | 11 +++------- .../SubmissionAccessOptionRestRepository.java | 2 +- .../AccessConditionAddPatchOperation.java | 3 ++- ...tionDiscoverableReplacePatchOperation.java | 3 ++- .../AccessConditionReplacePatchOperation.java | 3 ++- .../config/spring/api/access-conditions.xml | 9 ++------- 7 files changed, 20 insertions(+), 31 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfigurationService.java b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfigurationService.java index f517079aa3..cc3f9b1dee 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfigurationService.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/AccessConditionConfigurationService.java @@ -6,7 +6,10 @@ * http://www.dspace.org/license/ */ package org.dspace.submit.model; -import java.util.Map; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; /** * Simple bean to manage different Access Condition configurations @@ -15,18 +18,11 @@ import java.util.Map; */ public class AccessConditionConfigurationService { - /** - * Mapping the submission step process identifier with the configuration - * (see configuration at access-conditions.xml) - */ - private Map map; + @Autowired + private List accessConditionConfigurations; - public Map getMap() { - return map; - } - - public void setMap(Map map) { - this.map = map; + public AccessConditionConfiguration getAccessConfigurationById(String name) { + return accessConditionConfigurations.stream().filter(x -> name.equals(x.getName())).findFirst().get(); } } \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml index fac7fcc1af..e21a85cca4 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml @@ -60,16 +60,10 @@
    - - - - - - - - + + @@ -82,6 +76,7 @@ + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java index c70a351c15..a4117a6176 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionAccessOptionRestRepository.java @@ -34,7 +34,7 @@ public class SubmissionAccessOptionRestRepository extends DSpaceRestRepository/accessConditions/0/name" // the absolutePath will be : accessConditions/0 or accessConditions/0/name diff --git a/dspace/config/spring/api/access-conditions.xml b/dspace/config/spring/api/access-conditions.xml index 23d623af49..cd1550f389 100644 --- a/dspace/config/spring/api/access-conditions.xml +++ b/dspace/config/spring/api/access-conditions.xml @@ -61,15 +61,10 @@ - - - - - - - + + From 69fa1f94804d3ab7a92e0076fea1fccf0c78ab8e Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 28 Jan 2022 20:27:38 +0100 Subject: [PATCH 0681/1254] removed duplication check --- .../AccessConditionAddPatchOperation.java | 16 ------ .../AccessConditionReplacePatchOperation.java | 29 ++-------- .../rest/WorkspaceItemRestRepositoryIT.java | 56 ------------------- 3 files changed, 5 insertions(+), 96 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java index dfe4021c61..f6d5bed198 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionAddPatchOperation.java @@ -58,10 +58,6 @@ public class AccessConditionAddPatchOperation extends AddPatchOperation accessConditions) - throws SQLException { - List policies = resourcePolicyService.find(context, item, ResourcePolicy.TYPE_CUSTOM); - for (ResourcePolicy resourcePolicy : policies) { - for (AccessConditionDTO dto : accessConditions) { - if (dto.getName().equals(resourcePolicy.getRpName())) { - throw new UnprocessableEntityException("A policy of the same type already exists!"); - } - } - } - } - @Override protected Class getArrayClassForEvaluation() { return AccessConditionDTO[].class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java index db948cc789..0216628a6b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/AccessConditionReplacePatchOperation.java @@ -76,13 +76,11 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< // to replace an access condition with a new one AccessConditionDTO accessConditionDTO = evaluateSingleObject((LateObjectEvaluator) value); if (Objects.nonNull(accessConditionDTO) && Objects.nonNull(getOption(configuration, accessConditionDTO))) { - if (checkDuplication(context, policies, accessConditionDTO, idxToReplace, item)) { - item.getResourcePolicies().remove(policies.get(idxToReplace)); - resourcePolicyService.delete(context, policies.get(idxToReplace)); - AccessConditionOption option = getOption(configuration, accessConditionDTO); - option.createResourcePolicy(context, item, accessConditionDTO.getName(), null, - accessConditionDTO.getStartDate(),accessConditionDTO.getEndDate()); - } + item.getResourcePolicies().remove(policies.get(idxToReplace)); + resourcePolicyService.delete(context, policies.get(idxToReplace)); + AccessConditionOption option = getOption(configuration, accessConditionDTO); + option.createResourcePolicy(context, item, accessConditionDTO.getName(), null, + accessConditionDTO.getStartDate(),accessConditionDTO.getEndDate()); } } else if (absolutePath.length == 3) { // "/sections/<:name-of-the-form>/accessConditions/0/startDate" @@ -108,23 +106,6 @@ public class AccessConditionReplacePatchOperation extends ReplacePatchOperation< return null; } - private boolean checkDuplication(Context context, List policies, - AccessConditionDTO accessConditionDTO, int toReplace, Item item) - throws SQLException, AuthorizeException, ParseException { - ResourcePolicy policyToReplace = policies.get(toReplace); - // check if the resource policy is of the same type - if (policyToReplace.getRpName().equals(accessConditionDTO.getName())) { - return true; - } - // check if there is not already a policy of the same type - for (ResourcePolicy resourcePolicy : policies) { - if (resourcePolicy.getRpName().equals(accessConditionDTO.getName())) { - return false; - } - } - return true; - } - private AccessConditionDTO createDTO(ResourcePolicy rpToReplace, String attributeReplace, String valueToReplare) throws ParseException { AccessConditionDTO accessCondition = new AccessConditionDTO(); 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 0f38c3a9e5..91847bd50f 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 @@ -6258,62 +6258,6 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); } - @Test - public void patchAddDuplicateAccesConditionTest() throws Exception { - //disable file upload mandatory - configurationService.setProperty("webui.submit.upload.required", false); - 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("Example Title") - .withIssueDate("2019-11-21") - .withSubject("ExtraEntry") - .build(); - - ResourcePolicyBuilder.createResourcePolicy(context) - .withDspaceObject(witem.getItem()) - .withPolicyType(TYPE_CUSTOM) - .withName("openaccess") - .build(); - - context.restoreAuthSystemState(); - - String tokenAdmin = getAuthToken(admin.getEmail(), password); - - getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) - .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", - is("openaccess"))) - .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); - - List addAccessCondition = new ArrayList(); - Map accessCondition = new HashMap(); - accessCondition.put("name", "openaccess"); - addAccessCondition.add(new AddOperation("/sections/defaultAC/accessConditions/-", accessCondition)); - - String patchBody = getPatchContent(addAccessCondition); - getClient(tokenAdmin).perform(patch("/api/submission/workspaceitems/" + witem.getID()) - .content(patchBody) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isUnprocessableEntity()); - - getClient(tokenAdmin).perform(get("/api/submission/workspaceitems/" + witem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))) - .andExpect(jsonPath("$.sections.defaultAC.accessConditions[0].name", - is("openaccess"))) - .andExpect(jsonPath("$.sections.defaultAC.accessConditions[1].name").doesNotExist()); - } - @Test public void patchAddAccesConditionReplaceCompletelyTest() throws Exception { //disable file upload mandatory From dab8b2b9f8d497b62df822c22c20e9a3890056ea Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Sun, 30 Jan 2022 16:45:05 +0100 Subject: [PATCH 0682/1254] Changing the name of a group should not recalculate group2groupcache table --- dspace-api/src/main/java/org/dspace/eperson/Group.java | 1 - 1 file changed, 1 deletion(-) 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 09b5ce189b..22dc20526b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java @@ -201,7 +201,6 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport { void setName(String name) throws SQLException { if (!StringUtils.equals(this.name, name) && !isPermanent()) { this.name = name; - groupsChanged = true; } } From c15d9307f93827b8ce3e01f29b9177503cde9384 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Jan 2022 11:08:07 -0600 Subject: [PATCH 0683/1254] Update errorprone and mockito-inline to support Java 17 --- dspace-api/pom.xml | 7 ------- pom.xml | 24 +++++++++++++++++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 635163e990..02c9c10677 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -558,13 +558,6 @@ org.mockito mockito-inline test - - - - net.bytebuddy - byte-buddy - - org.springframework diff --git a/pom.xml b/pom.xml index 6e7b2e1ec0..2999d2ea1f 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 8.8.1 1.2.22 - 2.3.4 + 2.10.0 2.12.3 1.3.2 @@ -140,10 +140,21 @@ 3.8.1 11 - + + true -XDcompilePolicy=simple -Xplugin:ErrorProne + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED @@ -1642,7 +1653,7 @@ org.mockito mockito-inline - 3.8.0 + 3.12.4 test @@ -1757,6 +1768,13 @@ javax.annotation-api ${javax-annotation.version} + + + + net.bytebuddy + byte-buddy + 1.11.13 + From d928f84779fc7b99fb5035085eb641e7a037d81d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Jan 2022 11:09:01 -0600 Subject: [PATCH 0684/1254] Fix bugs reported by errorprone after it was upgraded --- .../dspace/statistics/content/StatisticsDataWorkflow.java | 5 +++-- .../src/main/java/org/purl/sword/client/CmdClient.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataWorkflow.java b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataWorkflow.java index c0148ca1d2..b39872a60c 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataWorkflow.java +++ b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataWorkflow.java @@ -13,6 +13,7 @@ import java.sql.SQLException; import java.text.ParseException; import java.time.LocalDate; import java.time.Period; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -158,8 +159,8 @@ public class StatisticsDataWorkflow extends StatisticsData { } private long getMonthsDifference(Date date1, Date date2) { - LocalDate earlier = LocalDate.from(date1.toInstant()); - LocalDate later = LocalDate.from(date2.toInstant()); + LocalDate earlier = LocalDate.ofInstant(date1.toInstant(), ZoneOffset.UTC); + LocalDate later = LocalDate.ofInstant(date2.toInstant(), ZoneOffset.UTC); return Period.between(earlier, later).toTotalMonths(); } diff --git a/dspace-sword/src/main/java/org/purl/sword/client/CmdClient.java b/dspace-sword/src/main/java/org/purl/sword/client/CmdClient.java index f747de5ffa..842e9d483d 100644 --- a/dspace-sword/src/main/java/org/purl/sword/client/CmdClient.java +++ b/dspace-sword/src/main/java/org/purl/sword/client/CmdClient.java @@ -161,7 +161,7 @@ public class CmdClient implements ClientType { for (Iterator i = acceptsPackaging.iterator(); i.hasNext(); ) { SwordAcceptPackaging accept = (SwordAcceptPackaging) i.next(); acceptPackagingList.append(accept.getContent()).append(" (").append(accept.getQualityValue()) - .append("), ").toString(); + .append("), "); } System.out.println("Accepts Packaging: " + acceptPackagingList.toString()); From 0c448c732dd2f5de18701f555dbf9a01c1b366e2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Jan 2022 11:09:27 -0600 Subject: [PATCH 0685/1254] Update CI build process to include both JDK 11 and 17 --- .github/workflows/build.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f633cc40cf..e023054ef9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,11 +17,17 @@ jobs: strategy: # Create a matrix of two separate configurations for Unit vs Integration Tests # This will ensure those tasks are run in parallel + # Also ensure both are run for multiple versions of Java. matrix: include: # NOTE: Unit Tests include deprecated REST API v6 (as it has unit tests) # - surefire.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries - - type: "Unit Tests" + - type: "Unit Tests on Java 11" + java: 11 + mvnflags: "-DskipUnitTests=false -Pdspace-rest -Dsurefire.rerunFailingTestsCount=2" + resultsdir: "**/target/surefire-reports/**" + - type: "Unit Tests on Java 17" + java: 17 mvnflags: "-DskipUnitTests=false -Pdspace-rest -Dsurefire.rerunFailingTestsCount=2" resultsdir: "**/target/surefire-reports/**" # NOTE: ITs skip all code validation checks, as they are already done by Unit Test job. @@ -30,7 +36,12 @@ jobs: # - license.skip => Skip all license header checks by license-maven-plugin # - xml.skip => Skip all XML/XSLT validation by xml-maven-plugin # - failsafe.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries - - type: "Integration Tests" + - type: "Integration Tests on Java 11" + java: 11 + mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true -Dfailsafe.rerunFailingTestsCount=2" + resultsdir: "**/target/failsafe-reports/**" + - type: "Integration Tests on Java 17" + java: 17 mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true -Dfailsafe.rerunFailingTestsCount=2" resultsdir: "**/target/failsafe-reports/**" # Do NOT exit immediately if one matrix job fails @@ -43,10 +54,10 @@ jobs: uses: actions/checkout@v2 # https://github.com/actions/setup-java - - name: Install JDK 11 + - name: Install JDK ${{ matrix.java }} uses: actions/setup-java@v2 with: - java-version: 11 + java-version: ${{ matrix.java }} distribution: 'temurin' # https://github.com/actions/cache @@ -73,7 +84,6 @@ jobs: with: name: ${{ matrix.type }} results path: ${{ matrix.resultsdir }} - retention-days: 7 # https://github.com/codecov/codecov-action - name: Upload coverage to Codecov.io From c6404c860d58d567f3fff728a0d74cc17d2de52c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Jan 2022 12:01:46 -0600 Subject: [PATCH 0686/1254] Add nicer name for each build in matrix --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e023054ef9..7887da8bd5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,7 @@ jobs: # Do NOT exit immediately if one matrix job fails # This ensures ITs continue running even if Unit Tests fail, or visa versa fail-fast: false + name: Run ${{ matrix.type }} # These are the actual CI steps to perform per job steps: # https://github.com/actions/checkout From 6bb2261d70ab89b34ea304bd67fe9040ebb9bbc6 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 13 Jan 2022 16:00:21 -0600 Subject: [PATCH 0687/1254] Revert to only running tests for JDK 11 --- .github/workflows/build.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7887da8bd5..4060dbd672 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,33 +17,25 @@ jobs: strategy: # Create a matrix of two separate configurations for Unit vs Integration Tests # This will ensure those tasks are run in parallel - # Also ensure both are run for multiple versions of Java. + # Also specify version of Java to use (this can allow us to optionally run tests on multiple JDKs in future) matrix: include: # NOTE: Unit Tests include deprecated REST API v6 (as it has unit tests) # - surefire.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries - - type: "Unit Tests on Java 11" + - type: "Unit Tests" java: 11 mvnflags: "-DskipUnitTests=false -Pdspace-rest -Dsurefire.rerunFailingTestsCount=2" resultsdir: "**/target/surefire-reports/**" - - type: "Unit Tests on Java 17" - java: 17 - mvnflags: "-DskipUnitTests=false -Pdspace-rest -Dsurefire.rerunFailingTestsCount=2" - resultsdir: "**/target/surefire-reports/**" # NOTE: ITs skip all code validation checks, as they are already done by Unit Test job. # - enforcer.skip => Skip maven-enforcer-plugin rules # - checkstyle.skip => Skip all checkstyle checks by maven-checkstyle-plugin # - license.skip => Skip all license header checks by license-maven-plugin # - xml.skip => Skip all XML/XSLT validation by xml-maven-plugin # - failsafe.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries - - type: "Integration Tests on Java 11" + - type: "Integration Tests" java: 11 mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true -Dfailsafe.rerunFailingTestsCount=2" resultsdir: "**/target/failsafe-reports/**" - - type: "Integration Tests on Java 17" - java: 17 - mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true -Dfailsafe.rerunFailingTestsCount=2" - resultsdir: "**/target/failsafe-reports/**" # Do NOT exit immediately if one matrix job fails # This ensures ITs continue running even if Unit Tests fail, or visa versa fail-fast: false From 96326dfde545aae838e40bf7bf53f82d19d73cd7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 13 Jan 2022 16:01:15 -0600 Subject: [PATCH 0688/1254] Update Dockerfiles to support either JDK11 or JDK17 (default is JDK11). Instructions in README --- Dockerfile | 17 +++++++++++------ Dockerfile.cli | 20 ++++++++++++-------- Dockerfile.dependencies | 21 +++++++++++++++------ Dockerfile.test | 17 +++++++++++------ dspace/src/main/docker-compose/README.md | 6 ++++++ 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/Dockerfile b/Dockerfile index 01d5e797f6..8a21c60a68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ # This image will be published as dspace/dspace # See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # -# This version is JDK11 compatible -# - tomcat:9-jdk11 -# - ANT 1.10.12 -# - maven:3-jdk-11 (see dspace-dependencies) # - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x +# This Dockerfile uses JDK11 by default, but has also been tested with JDK17. +# To build with JDK17, use "--build-arg JDK_VERSION=17" +ARG JDK_VERSION=11 + # Step 1 - Run Maven Build FROM dspace/dspace-dependencies:dspace-7_x as build ARG TARGET_DIR=dspace-installer @@ -25,7 +25,7 @@ RUN mvn package && \ mvn clean # Step 2 - Run Ant Deploy -FROM tomcat:9-jdk11 as ant_build +FROM openjdk:${JDK_VERSION}-slim as ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src @@ -34,6 +34,11 @@ WORKDIR /dspace-src ENV ANT_VERSION 1.10.12 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH +# Need wget to install ant +RUN apt-get update \ + && apt-get install -y --no-install-recommends wget \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* # Download and install 'ant' RUN mkdir $ANT_HOME && \ wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME @@ -42,7 +47,7 @@ RUN ant init_installation update_configs update_code update_webapps # Step 3 - Run tomcat # Create a new tomcat image that does not retain the the build directory contents -FROM tomcat:9-jdk11 +FROM tomcat:9-jdk${JDK_VERSION} # NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration. ENV DSPACE_INSTALL=/dspace # Copy the /dspace directory from 'ant_build' containger to /dspace in this container diff --git a/Dockerfile.cli b/Dockerfile.cli index c1bb1b1b15..e8966f7bb6 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -1,12 +1,12 @@ # This image will be published as dspace/dspace-cli # See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # -# This version is JDK11 compatible -# - openjdk:11 -# - ANT 1.10.12 -# - maven:3-jdk-11 (see dspace-dependencies) # - note: default tag for branch: dspace/dspace-cli: dspace/dspace-cli:dspace-7_x +# This Dockerfile uses JDK11 by default, but has also been tested with JDK17. +# To build with JDK17, use "--build-arg JDK_VERSION=17" +ARG JDK_VERSION=11 + # Step 1 - Run Maven Build FROM dspace/dspace-dependencies:dspace-7_x as build ARG TARGET_DIR=dspace-installer @@ -24,7 +24,7 @@ RUN mvn package && \ mvn clean # Step 2 - Run Ant Deploy -FROM openjdk:11 as ant_build +FROM openjdk:${JDK_VERSION}-slim as ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src @@ -33,6 +33,11 @@ WORKDIR /dspace-src ENV ANT_VERSION 1.10.12 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH +# Need wget to install ant +RUN apt-get update \ + && apt-get install -y --no-install-recommends wget \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* # Download and install 'ant' RUN mkdir $ANT_HOME && \ wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME @@ -40,11 +45,10 @@ RUN mkdir $ANT_HOME && \ RUN ant init_installation update_configs update_code # Step 3 - Run jdk -# Create a new tomcat image that does not retain the the build directory contents -FROM openjdk:11 +FROM openjdk:${JDK_VERSION} # NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration. ENV DSPACE_INSTALL=/dspace -# Copy the /dspace directory from 'ant_build' containger to /dspace in this container +# Copy the /dspace directory from 'ant_build' container to /dspace in this container COPY --from=ant_build /dspace $DSPACE_INSTALL # Give java extra memory (1GB) ENV JAVA_OPTS=-Xmx1000m diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies index 95d534ccbe..a55b323339 100644 --- a/Dockerfile.dependencies +++ b/Dockerfile.dependencies @@ -1,25 +1,34 @@ # This image will be published as dspace/dspace-dependencies # The purpose of this image is to make the build for dspace/dspace run faster # -# This version is JDK11 compatible -# - maven:3-jdk-11 + +# This Dockerfile uses JDK11 by default, but has also been tested with JDK17. +# To build with JDK17, use "--build-arg JDK_VERSION=17" +ARG JDK_VERSION=11 # Step 1 - Run Maven Build -FROM maven:3-jdk-11 as build +FROM maven:3-openjdk-${JDK_VERSION}-slim as build ARG TARGET_DIR=dspace-installer WORKDIR /app # Create the 'dspace' user account & home directory RUN useradd dspace \ - && mkdir /home/dspace \ + && mkdir -p /home/dspace \ && chown -Rv dspace: /home/dspace RUN chown -Rv dspace: /app +# Need git to support buildnumber-maven-plugin, which lets us know what version of DSpace is being run. +RUN apt-get update \ + && apt-get install -y --no-install-recommends git \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* + +# Switch to dspace user & run below commands as that user USER dspace # Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents) ADD --chown=dspace . /app/ -# Trigger the installation of all maven dependencies -RUN mvn package +# Trigger the installation of all maven dependencies (hide download progress messages) +RUN mvn --no-transfer-progress package # Clear the contents of the /app directory (including all maven builds), so no artifacts remain. # This ensures when dspace:dspace is built, it will use the Maven local cache (~/.m2) for dependencies diff --git a/Dockerfile.test b/Dockerfile.test index cbf541708b..568ff9b60a 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,14 +1,14 @@ # This image will be published as dspace/dspace # See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details # -# This version is JDK11 compatible -# - tomcat:9-jdk11 -# - ANT 1.10.12 -# - maven:3-jdk-11 (see dspace-dependencies) # - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-test # # This image is meant for TESTING/DEVELOPMENT ONLY as it deploys the old v6 REST API under HTTP (not HTTPS) +# This Dockerfile uses JDK11 by default, but has also been tested with JDK17. +# To build with JDK17, use "--build-arg JDK_VERSION=17" +ARG JDK_VERSION=11 + # Step 1 - Run Maven Build FROM dspace/dspace-dependencies:dspace-7_x as build ARG TARGET_DIR=dspace-installer @@ -27,7 +27,7 @@ RUN mvn package -Pdspace-rest && \ mvn clean # Step 2 - Run Ant Deploy -FROM tomcat:9-jdk11 as ant_build +FROM openjdk:${JDK_VERSION}-slim as ant_build ARG TARGET_DIR=dspace-installer # COPY the /install directory from 'build' container to /dspace-src in this container COPY --from=build /install /dspace-src @@ -36,6 +36,11 @@ WORKDIR /dspace-src ENV ANT_VERSION 1.10.12 ENV ANT_HOME /tmp/ant-$ANT_VERSION ENV PATH $ANT_HOME/bin:$PATH +# Need wget to install ant +RUN apt-get update \ + && apt-get install -y --no-install-recommends wget \ + && apt-get purge -y --auto-remove \ + && rm -rf /var/lib/apt/lists/* # Download and install 'ant' RUN mkdir $ANT_HOME && \ wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME @@ -44,7 +49,7 @@ RUN ant init_installation update_configs update_code update_webapps # Step 3 - Run tomcat # Create a new tomcat image that does not retain the the build directory contents -FROM tomcat:9-jdk11 +FROM tomcat:9-jdk${JDK_VERSION} ENV DSPACE_INSTALL=/dspace ENV TOMCAT_INSTALL=/usr/local/tomcat # Copy the /dspace directory from 'ant_build' containger to /dspace in this container diff --git a/dspace/src/main/docker-compose/README.md b/dspace/src/main/docker-compose/README.md index ac607fbe98..8e00ebbbf1 100644 --- a/dspace/src/main/docker-compose/README.md +++ b/dspace/src/main/docker-compose/README.md @@ -36,6 +36,12 @@ docker-compose -f docker-compose.yml -f docker-compose-cli.yml pull docker-compose -f docker-compose.yml -f docker-compose-cli.yml build ``` +OPTIONALLY, you can build DSpace images using a different JDK_VERSION like this: +``` +docker-compose -f docker-compose.yml -f docker-compose-cli.yml build --build-arg JDK_VERSION=17 +``` +Default is Java 11, but other LTS releases (e.g. 17) are also supported. + ## Run DSpace 7 REST from your current branch ``` docker-compose -p d7 up -d From c58a8b95375bfb078ee9738490e21b42d51ea87c Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 31 Jan 2022 12:15:24 -0800 Subject: [PATCH 0689/1254] Updated iiif config. --- dspace/config/modules/iiif.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index 084680d673..eee7702ed5 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -82,6 +82,6 @@ iiif.document.viewing.hint = individuals # If you want DSpace to retrieve accurate default dimensions for all images that lack height and width metadata, # set both values to be -1. These lookups can be expensive, so it's always best to update your bitstream # metadata with accurate iiif height and width dimensions for each image as soon as possible. -# iiif.canvas.default-width = 2400 -# iiif.canvas.default-height = 3600 +# iiif.canvas.default-width = 2200 +# iiif.canvas.default-height = 1600 From 9664296af6da68fad84aa1f318fbf7caf30a3a47 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 1 Feb 2022 14:48:51 +0100 Subject: [PATCH 0690/1254] 86201: Fix RelationshipService place handling Correctly take into account the place of other Relationships and/or MDVs when creating/modifying/deleting Relationships Simplify RelationshipService public API to avoid having to call updatePlaceInRelationship explicitly Additional tests to cover issues with the previous implementation --- .../dspace/app/bulkedit/MetadataImport.java | 9 +- .../content/DSpaceObjectServiceImpl.java | 22 +- .../content/RelationshipServiceImpl.java | 439 ++- .../dspace/content/dao/RelationshipDAO.java | 22 - .../content/dao/impl/RelationshipDAOImpl.java | 32 - .../content/service/RelationshipService.java | 64 +- .../dspace/builder/RelationshipBuilder.java | 3 +- .../RelationshipMetadataServiceIT.java | 43 - .../RelationshipServiceImplPlaceTest.java | 2363 ++++++++++++++++- .../content/RelationshipServiceImplTest.java | 26 - .../content/dao/RelationshipDAOImplTest.java | 22 - .../content/service/ItemServiceTest.java | 202 ++ .../RelationshipRestRepository.java | 41 +- .../rest/RelationshipRestRepositoryIT.java | 178 ++ 14 files changed, 3182 insertions(+), 284 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java 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 0db0cc45be..6b0330fc5a 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 @@ -924,11 +924,10 @@ public class MetadataImport extends DSpaceRunnable implements }); for (MetadataValue metadataValue : metadataValues) { //Retrieve & store the place for each metadata value - if (StringUtils.startsWith(metadataValue.getAuthority(), Constants.VIRTUAL_AUTHORITY_PREFIX) && - ((RelationshipMetadataValue) metadataValue).isUseForPlace()) { + if ( + // For virtual MDVs with useForPlace=true, + // update both the place of the metadatum and the place of the Relationship. + // E.g. for an Author relationship, + // the place should be updated using the same principle as dc.contributor.author. + StringUtils.startsWith(metadataValue.getAuthority(), Constants.VIRTUAL_AUTHORITY_PREFIX) + && ((RelationshipMetadataValue) metadataValue).isUseForPlace() + ) { int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue); metadataValue.setPlace(mvPlace); String authority = metadataValue.getAuthority(); @@ -650,8 +656,16 @@ public abstract class DSpaceObjectServiceImpl implements } relationshipService.update(context, relationship); - } else if (!StringUtils.startsWith(metadataValue.getAuthority(), - Constants.VIRTUAL_AUTHORITY_PREFIX)) { + } else if ( + // Otherwise, just set the place of the metadatum + // ...unless the metadatum in question is a relation.* metadatum. + // This case is a leftover from when a Relationship is removed and copied to metadata. + // If we let its place change the order of any remaining Relationships will be affected. + // todo: this makes it so these leftover MDVs can't be reordered later on + !StringUtils.equals( + metadataValue.getMetadataField().getMetadataSchema().getName(), "relation" + ) + ) { int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue); metadataValue.setPlace(mvPlace); } diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index 1b419da816..9b5b6d0b52 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -8,10 +8,13 @@ package org.dspace.content; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -24,6 +27,7 @@ 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.virtual.VirtualMetadataConfiguration; import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -97,7 +101,7 @@ public class RelationshipServiceImpl implements RelationshipService { // This order of execution should be handled in the creation (create, updateplace, update relationship) // for a proper place allocation Relationship relationshipToReturn = relationshipDAO.create(context, relationship); - updatePlaceInRelationship(context, relationshipToReturn); + updatePlaceInRelationship(context, relationshipToReturn, null, null, true, true); update(context, relationshipToReturn); updateItemsInRelationship(context, relationship); return relationshipToReturn; @@ -112,71 +116,364 @@ public class RelationshipServiceImpl implements RelationshipService { } @Override - public void updatePlaceInRelationship(Context context, Relationship relationship) - throws SQLException, AuthorizeException { + public Relationship move( + Context context, Relationship relationship, Integer newLeftPlace, Integer newRightPlace + ) throws SQLException, AuthorizeException { + if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || + authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) { + + // Don't do anything if neither the leftPlace nor rightPlace was updated + if (newLeftPlace != null || newRightPlace != null) { + // This order of execution should be handled in the creation (create, updateplace, update relationship) + // for a proper place allocation + updatePlaceInRelationship(context, relationship, newLeftPlace, newRightPlace, false, false); + update(context, relationship); + updateItemsInRelationship(context, relationship); + } + + return relationship; + } else { + throw new AuthorizeException( + "You do not have write rights on this relationship's items"); + } + } + + @Override + public Relationship move( + Context context, Relationship relationship, Item newLeftItem, Item newRightItem + ) throws SQLException, AuthorizeException { + // If the new Item is the same as the current Item, don't move + newLeftItem = newLeftItem != relationship.getLeftItem() ? newLeftItem : null; + newRightItem = newRightItem != relationship.getRightItem() ? newRightItem : null; + + // Don't do anything if neither the leftItem nor rightItem was updated + if (newLeftItem != null || newRightItem != null) { + // First move the Relationship to the back within the current Item's lists + // This ensures that we won't have any gaps once we move the Relationship to a different Item + move( + context, relationship, + newLeftItem != null ? -1 : null, + newRightItem != null ? -1 : null + ); + + boolean insertLeft = false; + boolean insertRight = false; + + // If Item has been changed, mark the previous Item as modified to make sure we discard the old relation.* + // metadata on the next update. + // Set the Relationship's Items to the new ones, appending to the end + if (newLeftItem != null) { + relationship.getLeftItem().setMetadataModified(); + relationship.setLeftItem(newLeftItem); + relationship.setLeftPlace(-1); + insertLeft = true; + } + if (newRightItem != null) { + relationship.getRightItem().setMetadataModified(); + relationship.setRightItem(newRightItem); + relationship.setRightPlace(-1); + insertRight = true; + } + + // This order of execution should be handled in the creation (create, updateplace, update relationship) + // for a proper place allocation + updatePlaceInRelationship(context, relationship, null, null, insertLeft, insertRight); + update(context, relationship); + updateItemsInRelationship(context, relationship); + } + return relationship; + } + + /** + * This method will update the place for the Relationship and all other relationships found by the items and + * relationship type of the given Relationship. + * + * @param context The relevant DSpace context + * @param relationship The Relationship object that will have its place updated and that will be used + * to retrieve the other relationships whose place might need to be updated. + * @param newLeftPlace If the Relationship in question is to be moved, the leftPlace it is to be moved to. + * Set this to null if the Relationship has not been moved, i.e. it has just been created, + * deleted or when its Items have been modified. + * @param newRightPlace If the Relationship in question is to be moved, the rightPlace it is to be moved to. + * Set this to null if the Relationship has not been moved, i.e. it has just been created, + * deleted or when its Items have been modified. + * @param insertLeft Whether the Relationship in question should be inserted into the left Item. + * Should be set to true when creating or moving to a different Item. + * @param insertRight Whether the Relationship in question should be inserted into the right Item. + * Should be set to true when creating or moving to a different Item. + * @throws SQLException If something goes wrong + * @throws AuthorizeException + * If the user is not authorized to update the Relationship or its Items + */ + private void updatePlaceInRelationship( + Context context, Relationship relationship, + Integer newLeftPlace, Integer newRightPlace, boolean insertLeft, boolean insertRight + ) throws SQLException, AuthorizeException { Item leftItem = relationship.getLeftItem(); - // Max value is used to ensure that these will get added to the back of the list and thus receive the highest - // (last) place as it's set to a -1 for creation - if (relationship.getLeftPlace() == -1) { - relationship.setLeftPlace(Integer.MAX_VALUE); - } Item rightItem = relationship.getRightItem(); - if (relationship.getRightPlace() == -1) { - relationship.setRightPlace(Integer.MAX_VALUE); - } - List leftRelationships = findByItemAndRelationshipType(context, - leftItem, - relationship.getRelationshipType(), true); - List rightRelationships = findByItemAndRelationshipType(context, - rightItem, - relationship.getRelationshipType(), - false); - // These relationships are only deleted from the temporary lists incase they're present in them so that we can + List leftRelationships = findByItemAndRelationshipType( + context, leftItem, relationship.getRelationshipType(), true + ); + List rightRelationships = findByItemAndRelationshipType( + context, rightItem, relationship.getRelationshipType(), false + ); + + // These relationships are only deleted from the temporary lists in case they're present in them so that we can // properly perform our place calculation later down the line in this method. - if (leftRelationships.contains(relationship)) { - leftRelationships.remove(relationship); - } - if (rightRelationships.contains(relationship)) { - rightRelationships.remove(relationship); - } + boolean deletedFromLeft = !leftRelationships.contains(relationship); + boolean deletedFromRight = !rightRelationships.contains(relationship); + leftRelationships.remove(relationship); + rightRelationships.remove(relationship); + + List leftMetadata = getSiblingMetadata(leftItem, relationship, true); + List rightMetadata = getSiblingMetadata(rightItem, relationship, false); + + // For new relationships added to the end, this will be -1. + // For new relationships added at a specific position, this will contain that position. + // For existing relationships, this will contain the place before it was moved. + // For deleted relationships, this will contain the place before it was deleted. + int oldLeftPlace = relationship.getLeftPlace(); + int oldRightPlace = relationship.getRightPlace(); + + boolean movedUpLeft = resolveRelationshipPlace( + relationship, true, leftRelationships, leftMetadata, oldLeftPlace, newLeftPlace + ); + boolean movedUpRight = resolveRelationshipPlace( + relationship, false, rightRelationships, rightMetadata, oldRightPlace, newRightPlace + ); + context.turnOffAuthorisationSystem(); - //If useForPlace for the leftwardType is false for the relationshipType, - // we need to sort the relationships here based on leftplace. - if (!virtualMetadataPopulator.isUseForPlaceTrueForRelationshipType(relationship.getRelationshipType(), true)) { - if (!leftRelationships.isEmpty()) { - leftRelationships.sort(Comparator.comparingInt(Relationship::getLeftPlace)); - for (int i = 0; i < leftRelationships.size(); i++) { - leftRelationships.get(i).setLeftPlace(i); - } - relationship.setLeftPlace(leftRelationships.size()); - } else { - relationship.setLeftPlace(0); - } - } else { - updateItem(context, leftItem); - } + shiftSiblings( + relationship, true, oldLeftPlace, movedUpLeft, insertLeft, deletedFromLeft, + leftRelationships, leftMetadata + ); + shiftSiblings( + relationship, false, oldRightPlace, movedUpRight, insertRight, deletedFromRight, + rightRelationships, rightMetadata + ); - //If useForPlace for the rightwardType is false for the relationshipType, - // we need to sort the relationships here based on the rightplace. - if (!virtualMetadataPopulator.isUseForPlaceTrueForRelationshipType(relationship.getRelationshipType(), false)) { - if (!rightRelationships.isEmpty()) { - rightRelationships.sort(Comparator.comparingInt(Relationship::getRightPlace)); - for (int i = 0; i < rightRelationships.size(); i++) { - rightRelationships.get(i).setRightPlace(i); - } - relationship.setRightPlace(rightRelationships.size()); - } else { - relationship.setRightPlace(0); - } + updateItem(context, leftItem); + updateItem(context, rightItem); - } else { - updateItem(context, rightItem); - - } context.restoreAuthSystemState(); + } + /** + * Return the MDVs in the Item's MDF corresponding to the given Relationship. + * Return an empty list if the Relationship isn't mapped to any MDF + * or if the mapping is configured with useForPlace=false. + * + * This returns actual metadata (not virtual) which in the same metadata field as the useForPlace. + * For a publication with 2 author relationships and 3 plain text dc.contributor.author values, + * it would return the 3 plain text dc.contributor.author values. + * For a person related to publications, it would return an empty list. + */ + private List getSiblingMetadata( + Item item, Relationship relationship, boolean isLeft + ) { + List metadata = new ArrayList<>(); + if (virtualMetadataPopulator.isUseForPlaceTrueForRelationshipType(relationship.getRelationshipType(), isLeft)) { + HashMap mapping; + if (isLeft) { + mapping = virtualMetadataPopulator.getMap().get(relationship.getRelationshipType().getLeftwardType()); + } else { + mapping = virtualMetadataPopulator.getMap().get(relationship.getRelationshipType().getRightwardType()); + } + if (mapping != null) { + for (String mdf : mapping.keySet()) { + metadata.addAll( + // Make sure we're only looking at database MDVs; if the relationship currently overlaps + // one of these, its virtual MDV will overwrite the database MDV in itemService.getMetadata() + // The relationship pass should be sufficient to move any sibling virtual MDVs. + item.getMetadata() + .stream() + .filter(mdv -> mdv.getMetadataField().toString().equals(mdf.replace(".", "_"))) + .collect(Collectors.toList()) + ); + } + } + } + return metadata; + } + + /** + * Set the left/right place of a Relationship + * - To a new place in case it's being moved + * - Resolve -1 to the actual last place based on the places of its sibling Relationships and/or MDVs + * and determine if it has been moved up in the list. + * + * Examples: + * - Insert a Relationship at place 3 + * newPlace starts out as null and is not updated. Return movedUp=false + * - Insert a Relationship at place -1 + * newPlace starts out as null and is resolved to e.g. 6. Update the Relationship and return movedUp=false + * - Move a Relationship from place 4 to 2 + * Update the Relationship and return movedUp=false. + * - Move a Relationship from place 2 to -1 + * newPlace starts out as -1 and is resolved to e.g. 5. Update the relationship and return movedUp=true. + * - Remove a relationship from place 1 + * Return movedUp=false + * + * @param relationship the Relationship that's being updated + * @param isLeft whether to consider the left side of the Relationship. + * This method should be called twice, once with isLeft=true and once with isLeft=false. + * Make sure this matches the provided relationships/metadata/oldPlace/newPlace. + * @param relationships the list of sibling Relationships + * @param metadata the list of sibling MDVs + * @param oldPlace the previous place for this Relationship, in case it has been moved. + * Otherwise, the current place of a deleted Relationship + * or the place a Relationship has been inserted. + * @param newPlace The new place for this Relationship. Will be null on insert/delete. + * @return true if the Relationship was moved and newPlace > oldPlace + */ + private boolean resolveRelationshipPlace( + Relationship relationship, boolean isLeft, + List relationships, List metadata, + int oldPlace, Integer newPlace + ) { + boolean movedUp = false; + + if (newPlace != null) { + // We're moving an existing Relationship... + if (newPlace == -1) { + // ...to the end of the list + int nextPlace = getNextPlace(relationships, metadata, isLeft); + if (nextPlace == oldPlace) { + // If this Relationship is already at the end, do nothing. + newPlace = oldPlace; + } else { + // Subtract 1 from the next place since we're moving, not inserting and + // the total number of Relationships stays the same. + newPlace = nextPlace - 1; + } + } + if (newPlace > oldPlace) { + // ...up the list. We have to keep track of this in order to shift correctly later on + movedUp = true; + } + } else if (oldPlace == -1) { + // We're _not_ moving an existing Relationship. The newPlace is already set in the Relationship object. + // We only need to resolve it to the end of the list if it's set to -1, otherwise we can just keep it as is. + newPlace = getNextPlace(relationships, metadata, isLeft); + } + + if (newPlace != null) { + setPlace(relationship, isLeft, newPlace); + } + + return movedUp; + } + + /** + * Return the index of the next place in a list of Relationships and Metadata. + * By not relying on the size of both lists we can support one-to-many virtual MDV mappings. + * @param isLeft whether to take the left or right place of each Relationship + */ + private int getNextPlace(List relationships, List metadata, boolean isLeft) { + return Stream.concat( + metadata.stream().map(MetadataValue::getPlace), + relationships.stream().map(r -> getPlace(r, isLeft)) + ).max(Integer::compare) + .map(integer -> integer + 1) + .orElse(0); + } + + /** + * Adjust the left/right place of sibling Relationships and MDVs + * + * Examples: with sibling Relationships R,S,T and metadata a,b,c + * - Insert T at place 1 aRbSc -> a T RbSc + * Shift all siblings with place >= 1 one place to the right + * - Delete R from place 2 aT R bSc -> aTbSc + * Shift all siblings with place > 2 one place to the left + * - Move S from place 3 to place 2 (movedUp=false) aTb S c -> aT S bc + * Shift all siblings with 2 < place <= 3 one place to the right + * - Move T from place 1 to place 3 (movedUp=true) a T Sbc -> aSb T c + * Shift all siblings with 1 < place <= 3 one place to the left + * + * @param relationship the Relationship that's being updated + * @param isLeft whether to consider the left side of the Relationship. + * This method should be called twice, once with isLeft=true and once with isLeft=false. + * Make sure this matches the provided relationships/metadata/oldPlace/newPlace. + * @param oldPlace the previous place for this Relationship, in case it has been moved. + * Otherwise, the current place of a deleted Relationship + * or the place a Relationship has been inserted. + * @param movedUp if this Relationship has been moved up the list, e.g. from place 2 to place 4 + * @param deleted whether this Relationship has been deleted + * @param relationships the list of sibling Relationships + * @param metadata the list of sibling MDVs + */ + private void shiftSiblings( + Relationship relationship, boolean isLeft, int oldPlace, boolean movedUp, boolean inserted, boolean deleted, + List relationships, List metadata + ) { + int newPlace = getPlace(relationship, isLeft); + + for (Relationship sibling : relationships) { + int siblingPlace = getPlace(sibling, isLeft); + if ( + (deleted && siblingPlace > newPlace) + // If the relationship was deleted, all relationships after it should shift left + // We must make the distinction between deletes and moves because for inserts oldPlace == newPlace + || (movedUp && siblingPlace <= newPlace && siblingPlace > oldPlace) + // If the relationship was moved up e.g. from place 2 to 5, all relationships + // with place > 2 (the old place) and <= to 5 should shift left + ) { + setPlace(sibling, isLeft, siblingPlace - 1); + } else if ( + (inserted && siblingPlace >= newPlace) + // If the relationship was inserted, all relationships starting from that place should shift right + // We must make the distinction between inserts and moves because for inserts oldPlace == newPlace + || (!movedUp && siblingPlace >= newPlace && siblingPlace < oldPlace) + // If the relationship was moved down e.g. from place 5 to 2, all relationships + // with place >= 2 and < 5 (the old place) should shift right + ) { + setPlace(sibling, isLeft, siblingPlace + 1); + } + } + for (MetadataValue mdv : metadata) { + int mdvPlace = mdv.getPlace(); + if ( + (deleted && mdvPlace > newPlace) + // If the relationship was deleted, all metadata after it should shift left + // We must make the distinction between deletes and moves because for inserts oldPlace == newPlace + // If the reltionship was copied to metadata on deletion: + // - the plain text can be after the relationship (in which case it's moved forward again) + // - or before the relationship (in which case it remains in place) + || (movedUp && mdvPlace <= newPlace && mdvPlace > oldPlace) + // If the relationship was moved up e.g. from place 2 to 5, all metadata + // with place > 2 (the old place) and <= to 5 should shift left + ) { + mdv.setPlace(mdvPlace - 1); + } else if ( + (inserted && mdvPlace >= newPlace) + // If the relationship was inserted, all relationships starting from that place should shift right + // We must make the distinction between inserts and moves because for inserts oldPlace == newPlace + || (!movedUp && mdvPlace >= newPlace && mdvPlace < oldPlace) + // If the relationship was moved down e.g. from place 5 to 2, all relationships + // with place >= 2 and < 5 (the old place) should shift right + ) { + mdv.setPlace(mdvPlace + 1); + } + } + } + + private int getPlace(Relationship relationship, boolean isLeft) { + if (isLeft) { + return relationship.getLeftPlace(); + } else { + return relationship.getRightPlace(); + } + } + + private void setPlace(Relationship relationship, boolean isLeft, int place) { + if (isLeft) { + relationship.setLeftPlace(place); + } else { + relationship.setRightPlace(place); + } } @Override @@ -186,16 +483,6 @@ public class RelationshipServiceImpl implements RelationshipService { itemService.update(context, relatedItem); } - @Override - public int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException { - return relationshipDAO.findNextLeftPlaceByLeftItem(context, item); - } - - @Override - public int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException { - return relationshipDAO.findNextRightPlaceByRightItem(context, item); - } - private boolean isRelationshipValidToCreate(Context context, Relationship relationship) throws SQLException { RelationshipType relationshipType = relationship.getRelationshipType(); @@ -375,7 +662,7 @@ public class RelationshipServiceImpl implements RelationshipService { if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) { relationshipDAO.delete(context, relationship); - updatePlaceInRelationship(context, relationship); + updatePlaceInRelationship(context, relationship, null, null, false, false); updateItemsInRelationship(context, relationship); } else { throw new AuthorizeException( @@ -508,6 +795,9 @@ public class RelationshipServiceImpl implements RelationshipService { /** * Converts virtual metadata from RelationshipMetadataValue objects to actual item metadata. + * The resulting MDVs are added in front or behind the Relationship's virtual MDVs. + * The Relationship's virtual MDVs may be shifted right, and all subsequent metadata will be shifted right. + * So this method ensures the places are still valid. * * @param context The relevant DSpace context * @param relationship The relationship containing the left and right items @@ -524,7 +814,15 @@ public class RelationshipServiceImpl implements RelationshipService { relationshipMetadataService.findRelationshipMetadataValueForItemRelationship(context, relationship.getLeftItem(), entityTypeString, relationship, true); for (RelationshipMetadataValue relationshipMetadataValue : relationshipMetadataValues) { - itemService.addAndShiftRightMetadata(context, relationship.getLeftItem(), + // This adds the plain text metadata values on the same spot as the virtual values. + // This will be overruled in org.dspace.content.DSpaceObjectServiceImpl.update + // in the line below but it's not important whether the plain text or virtual values end up on top. + // The virtual values will eventually be deleted, and the others shifted + // This is required because addAndShiftRightMetadata has issues on metadata fields containing + // relationship values which are not useForPlace, while the relationhip type has useForPlace + // E.g. when using addAndShiftRightMetadata on relation.isAuthorOfPublication, it will break the order + // from dc.contributor.author + itemService.addMetadata(context, relationship.getLeftItem(), relationshipMetadataValue.getMetadataField(). getMetadataSchema().getName(), relationshipMetadataValue.getMetadataField().getElement(), @@ -533,6 +831,7 @@ public class RelationshipServiceImpl implements RelationshipService { relationshipMetadataValue.getValue(), null, -1, relationshipMetadataValue.getPlace()); } + //This will ensure the new values no longer overlap, but won't break the order itemService.update(context, relationship.getLeftItem()); } if (copyToRightItem) { @@ -542,7 +841,7 @@ public class RelationshipServiceImpl implements RelationshipService { relationshipMetadataService.findRelationshipMetadataValueForItemRelationship(context, relationship.getRightItem(), entityTypeString, relationship, true); for (RelationshipMetadataValue relationshipMetadataValue : relationshipMetadataValues) { - itemService.addAndShiftRightMetadata(context, relationship.getRightItem(), + itemService.addMetadata(context, relationship.getRightItem(), relationshipMetadataValue.getMetadataField(). getMetadataSchema().getName(), relationshipMetadataValue.getMetadataField().getElement(), diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java index e28cd0b6ac..2e31ca88fc 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java @@ -53,28 +53,6 @@ public interface RelationshipDAO extends GenericDAO { List findByItem(Context context, Item item, Integer limit, Integer offset, boolean excludeTilted) throws SQLException; - /** - * This method returns the next leftplace integer to use for a relationship with this item as the leftItem - * - * @param context The relevant DSpace context - * @param item The item to be matched on leftItem - * @return The next integer to be used for the leftplace of a relationship with the given item - * as a left item - * @throws SQLException If something goes wrong - */ - int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException; - - /** - * This method returns the next rightplace integer to use for a relationship with this item as the rightItem - * - * @param context The relevant DSpace context - * @param item The item to be matched on rightItem - * @return The next integer to be used for the rightplace of a relationship with the given item - * as a right item - * @throws SQLException If something goes wrong - */ - int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException; - /** * This method returns a list of Relationship objects for the given RelationshipType object. * It will construct a list of all Relationship objects that have the given RelationshipType object diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index db1aef96a2..feac778c86 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -85,38 +85,6 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl return count(context, criteriaQuery, criteriaBuilder, relationshipRoot); } - @Override - public int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException { - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); - Root relationshipRoot = criteriaQuery.from(Relationship.class); - criteriaQuery.select(relationshipRoot); - criteriaQuery.where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item)); - List list = list(context, criteriaQuery, false, Relationship.class, -1, -1); - list.sort((o1, o2) -> o2.getLeftPlace() - o1.getLeftPlace()); - if (!list.isEmpty()) { - return list.get(0).getLeftPlace() + 1; - } else { - return 0; - } - } - - @Override - public int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException { - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); - Root relationshipRoot = criteriaQuery.from(Relationship.class); - criteriaQuery.select(relationshipRoot); - criteriaQuery.where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item)); - List list = list(context, criteriaQuery, false, Relationship.class, -1, -1); - list.sort((o1, o2) -> o2.getRightPlace() - o1.getRightPlace()); - if (!list.isEmpty()) { - return list.get(0).getRightPlace() + 1; - } else { - return 0; - } - } - @Override public List findByRelationshipType(Context context, RelationshipType relationshipType) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index fab4616ef3..ad0f252b28 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -78,26 +78,49 @@ public interface RelationshipService extends DSpaceCRUDService { public Relationship create(Context context, Relationship relationship) throws SQLException, AuthorizeException; /** - * This method returns the next leftplace integer to use for a relationship with this item as the leftItem + * Move the given relationship to a new leftPlace and/or rightPlace. * - * @param context The relevant DSpace context - * @param item The item that has to be the leftItem of a relationship for it to qualify - * @return The next integer to be used for the leftplace of a relationship with the given item - * as a left item - * @throws SQLException If something goes wrong + * This will + * 1. verify whether the move is authorized + * 2. move the relationship to the specified left/right place + * 3. update the left/right place of other relationships and/or metadata in order to resolve the move without + * leaving any gaps + * + * At least one of the new places should be non-null, otherwise no changes will be made. + * + * @param context The relevant DSpace context + * @param relationship The Relationship to move + * @param newLeftPlace The value to set the leftPlace of this Relationship to + * @param newRightPlace The value to set the rightPlace of this Relationship to + * @return The moved relationship with updated place variables + * @throws SQLException If something goes wrong + * @throws AuthorizeException If the user is not authorized to update the Relationship or its Items */ - int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException; + Relationship move(Context context, Relationship relationship, Integer newLeftPlace, Integer newRightPlace) + throws SQLException, AuthorizeException; /** - * This method returns the next rightplace integer to use for a relationship with this item as the rightItem + * Move the given relationship to a new leftItem and/or rightItem. * - * @param context The relevant DSpace context - * @param item The item that has to be the rightitem of a relationship for it to qualify - * @return The next integer to be used for the rightplace of a relationship with the given item - * as a right item - * @throws SQLException If something goes wrong + * This will + * 1. move the relationship to the last place in its current left or right Item. This ensures that we don't leave + * any gaps when moving the relationship to a new Item. + * If only one of the relationship's Items is changed,the order of relationships and metadatain the other + * will not be affected + * 2. insert the relationship into the new Item(s) + * + * At least one of the new Items should be non-null, otherwise no changes will be made. + * + * @param context The relevant DSpace context + * @param relationship The Relationship to move + * @param newLeftItem The value to set the leftItem of this Relationship to + * @param newRightItem The value to set the rightItem of this Relationship to + * @return The moved relationship with updated left/right Items variables + * @throws SQLException If something goes wrong + * @throws AuthorizeException If the user is not authorized to update the Relationship or its Items */ - int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException; + Relationship move(Context context, Relationship relationship, Item newLeftItem, Item newRightItem) + throws SQLException, AuthorizeException; /** * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given @@ -143,19 +166,6 @@ public interface RelationshipService extends DSpaceCRUDService { int limit, int offset) throws SQLException; - /** - * This method will update the place for the Relationship and all other relationships found by the items and - * relationship type of the given Relationship. It will give this Relationship the last place in both the - * left and right place determined by querying for the list of leftRelationships and rightRelationships - * by the leftItem, rightItem and relationshipType of the given Relationship. - * @param context The relevant DSpace context - * @param relationship The Relationship object that will have it's place updated and that will be used - * to retrieve the other relationships whose place might need to be updated - * @throws SQLException If something goes wrong - */ - public void updatePlaceInRelationship(Context context, Relationship relationship) - throws SQLException, AuthorizeException; - /** * This method will update the given item's metadata order. * If the relationships for the item have been modified and will calculate the place based on a diff --git a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java index 8746033419..32aa09d868 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java @@ -117,7 +117,8 @@ public class RelationshipBuilder extends AbstractBuilder should remain unchanged + assertLeftPlace(r1, 0); + assertLeftPlace(r2, 1); + assertLeftPlace(r3, 2); + assertRelationMetadataOrder(author1, isProjectOfPerson, List.of(r1, r2, r3)); + } + + @Test + public void changeRightItemInNonUseForPlaceRelationshipAtTheStartToSameItemNoChanges() throws Exception { + context.turnOffAuthorisationSystem(); + + // Add three Projects to author1, appending to the end + Relationship r1 = relationshipService.create(context, author1, project1, isProjectOfPerson, -1, -1); + Relationship r2 = relationshipService.create(context, author1, project2, isProjectOfPerson, -1, -1); + Relationship r3 = relationshipService.create(context, author1, project3, isProjectOfPerson, -1, -1); + + // Move r1 to author1 + relationshipService.move(context, r1, null, project1); + + context.restoreAuthSystemState(); + + // Check relationship order for author1 -> should remain unchanged + assertLeftPlace(r1, 0); + assertLeftPlace(r2, 1); + assertLeftPlace(r3, 2); + assertRelationMetadataOrder(author1, isProjectOfPerson, List.of(r1, r2, r3)); + } + + @Test + public void changeLeftItemInNonUseForPlaceRelationshipAtTheStartWithSiblingsInOldLeftItem() throws Exception { + context.turnOffAuthorisationSystem(); + + // Add three Projects to author1, appending to the end + Relationship r1 = relationshipService.create(context, author1, project1, isProjectOfPerson, -1, -1); + Relationship r2 = relationshipService.create(context, author1, project2, isProjectOfPerson, -1, -1); + Relationship r3 = relationshipService.create(context, author1, project3, isProjectOfPerson, -1, -1); + + // Add three Authors to project1, appending to the end + Relationship r4 = relationshipService.create(context, author4, project1, isProjectOfPerson, -1, -1); + Relationship r5 = relationshipService.create(context, author5, project1, isProjectOfPerson, -1, -1); + Relationship r6 = relationshipService.create(context, author6, project1, isProjectOfPerson, -1, -1); + + // Move r1 to author2 + relationshipService.move(context, r1, author2, null); + + context.restoreAuthSystemState(); + + // Check relationship order for author1 -> should shift down by one + assertLeftPlace(r2, 0); + assertLeftPlace(r3, 1); + assertRelationMetadataOrder(author1, isProjectOfPerson, List.of(r2, r3)); + + // Check relationship order for project 1 -> should remain unchanged + assertRightPlace(r1, 0); + assertRightPlace(r4, 1); + assertRightPlace(r5, 2); + assertRightPlace(r6, 3); + assertRelationMetadataOrder(project1, isProjectOfPerson, List.of(r1, r4, r5, r6)); + } + + @Test + public void changeRightItemInNonUseForPlaceRelationshipAtTheStartWithSiblingsInOldRightItem() throws Exception { + context.turnOffAuthorisationSystem(); + + // Add three Authors to project1, appending to the end + Relationship r1 = relationshipService.create(context, author1, project1, isProjectOfPerson, -1, -1); + Relationship r2 = relationshipService.create(context, author2, project1, isProjectOfPerson, -1, -1); + Relationship r3 = relationshipService.create(context, author3, project1, isProjectOfPerson, -1, -1); + + // Add three Projects to author1, appending to the end + Relationship r4 = relationshipService.create(context, author1, project4, isProjectOfPerson, -1, -1); + Relationship r5 = relationshipService.create(context, author1, project5, isProjectOfPerson, -1, -1); + Relationship r6 = relationshipService.create(context, author1, project6, isProjectOfPerson, -1, -1); + + // Move r1 to project2 + relationshipService.move(context, r1, null, project2); + + context.restoreAuthSystemState(); + + // Check relationship order for project1 -> should remain unchanged + assertRightPlace(r2, 0); + assertRightPlace(r3, 1); + assertRelationMetadataOrder(project1, isProjectOfPerson, List.of(r2, r3)); + + // Check relationship order for author1 -> should remain unchanged + assertLeftPlace(r1, 0); + assertLeftPlace(r4, 1); + assertLeftPlace(r5, 2); + assertLeftPlace(r6, 3); + assertRelationMetadataOrder(author1, isProjectOfPerson, List.of(r1, r4, r5, r6)); + } + + + private void assertLeftPlace(Relationship relationship, int leftPlace) { + assertEquals(leftPlace, relationship.getLeftPlace()); + } + + private void assertRightPlace(Relationship relationship, int rightPlace) { + assertEquals(rightPlace, relationship.getRightPlace()); + } + + + private void assertRelationMetadataOrder( + Item item, RelationshipType relationshipType, List relationships + ) { + String element = getRelationshipTypeStringForEntity(relationshipType, item); + List mdvs = itemService.getMetadata( + item, + MetadataSchemaEnum.RELATION.getName(), element, null, + Item.ANY + ); + + assertEquals( + "Metadata authorities should match relationship IDs", + relationships.stream() + .map(r -> { + if (r != null) { + return Constants.VIRTUAL_AUTHORITY_PREFIX + r.getID(); + } else { + return null; // To match relationships that have been deleted and copied to MDVs + } + }) + .collect(Collectors.toList()), + mdvs.stream() + .map(MetadataValue::getAuthority) + .collect(Collectors.toList()) + ); + } + + private void assertMetadataOrder( + Item item, String metadataField, List metadataValues + ) { + List mdvs = itemService.getMetadataByMetadataString(item, metadataField); + + assertEquals( + metadataValues, + mdvs.stream() + .map(MetadataValue::getValue) + .collect(Collectors.toList()) + ); + } + + private String getRelationshipTypeStringForEntity(RelationshipType relationshipType, Item item) { + String entityType = relationshipMetadataService.getEntityTypeStringFromMetadata(item); + + if (StringUtils.equals(entityType, relationshipType.getLeftType().getLabel())) { + return relationshipType.getLeftwardType(); + } else if (StringUtils.equals(entityType, relationshipType.getRightType().getLabel())) { + return relationshipType.getRightwardType(); + } else { + throw new IllegalArgumentException( + entityType + "is not a valid entity for " + relationshipType.getLeftwardType() + ", must be either " + + relationshipType.getLeftType().getLabel() + " or " + relationshipType.getRightType().getLabel() + ); + } + } } diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java index 5d6197e494..49983bfe66 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java @@ -122,32 +122,6 @@ public class RelationshipServiceImplTest { } } - @Test - public void testFindLeftPlaceByLeftItem() throws Exception { - // Declare objects utilized in unit test - Item item = mock(Item.class); - - // Mock DAO to return mocked left place as 0 - when(relationshipDAO.findNextLeftPlaceByLeftItem(context, item)).thenReturn(0); - - // The left place reported from out mocked item should match the DAO's report of the left place - assertEquals("TestFindLeftPlaceByLeftItem 0", relationshipDAO.findNextLeftPlaceByLeftItem(context, item), - relationshipService.findNextLeftPlaceByLeftItem(context, item)); - } - - @Test - public void testFindRightPlaceByRightItem() throws Exception { - // Declare objects utilized in unit test - Item item = mock(Item.class); - - // Mock lower level DAO to return mocked right place as 0 - when(relationshipDAO.findNextRightPlaceByRightItem(context, item)).thenReturn(0); - - // The right place reported from out mocked item should match the DAO's report of the right place - assertEquals("TestFindRightPlaceByRightItem 0", relationshipDAO.findNextRightPlaceByRightItem(context, item), - relationshipService.findNextRightPlaceByRightItem(context, item)); - } - @Test public void testFindByItemAndRelationshipType() throws Exception { // Declare objects utilized in unit test diff --git a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java index 2143090fcf..b6f5da6be0 100644 --- a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java @@ -138,28 +138,6 @@ public class RelationshipDAOImplTest extends AbstractIntegrationTest { -1, -1, false)); } - /** - * Test findNextLeftPlaceByLeftItem should return 0 given our test left Item itemOne. - * - * @throws Exception - */ - @Test - public void testFindNextLeftPlaceByLeftItem() throws Exception { - assertEquals("TestNextLeftPlaceByLeftItem 0", 1, relationshipService.findNextLeftPlaceByLeftItem(context, - itemOne)); - } - - /** - * Test findNextRightPlaceByRightItem should return 0 given our test right Item itemTwo. - * - * @throws Exception - */ - @Test - public void testFindNextRightPlaceByRightItem() throws Exception { - assertEquals("TestNextRightPlaceByRightItem 0", 1, relationshipService.findNextRightPlaceByRightItem(context, - itemTwo)); - } - /** * Test findByRelationshipType should return our defined relationshipsList given our test RelationshipType * relationshipType 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 new file mode 100644 index 0000000000..c66a6eed76 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.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.content.service; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.fail; + +import java.sql.SQLException; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.Logger; +import org.dspace.AbstractUnitTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; +import org.junit.Before; +import org.junit.Test; + +public class ItemServiceTest extends AbstractUnitTest { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemServiceTest.class); + + protected RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + protected RelationshipTypeService relationshipTypeService = ContentServiceFactory.getInstance() + .getRelationshipTypeService(); + protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); + + Community community; + Collection col; + + Item item; + + String authorQualifier = "author"; + String contributorElement = "contributor"; + String dcSchema = "dc"; + + /** + * This method will be run before every test as per @Before. It will + * initialize resources required for the tests. + */ + @Before + @Override + public void init() { + super.init(); + try { + context.turnOffAuthorisationSystem(); + community = communityService.create(null, context); + + col = collectionService.create(context, community); + WorkspaceItem is = workspaceItemService.create(context, col, false); + WorkspaceItem authorIs = workspaceItemService.create(context, col, false); + + item = installItemService.installItem(context, is); + itemService.addMetadata(context, item, "dspace", "entity", "type", null, "Publication"); + + context.restoreAuthSystemState(); + } 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: " + ex.getMessage()); + } + } + + @Test + public void InsertAndMoveMetadataShiftPlaceTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // Here we add the first set of metadata to the item + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, one"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, two"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, three"); + + context.restoreAuthSystemState(); + + // The code below performs the mentioned assertions to ensure the place is correct + List list = itemService + .getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + assertThat(list.size(), equalTo(3)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2)); + + context.turnOffAuthorisationSystem(); + + // This is where we add metadata at place=1 + itemService.addAndShiftRightMetadata( + context, item, dcSchema, contributorElement, authorQualifier, null, "test, four", null, -1, 1 + ); + + // Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY) + .stream() + .sorted(Comparator.comparingInt(MetadataValue::getPlace)) + .collect(Collectors.toList()); + assertThat(list.size(), equalTo(4)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 2, list.get(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3)); + + // And move metadata from place=2 to place=0 + itemService.moveMetadata(context, item, dcSchema, contributorElement, authorQualifier, 2, 0); + + context.restoreAuthSystemState(); + + // Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY) + .stream() + .sorted(Comparator.comparingInt(MetadataValue::getPlace)) + .collect(Collectors.toList()); + assertThat(list.size(), equalTo(4)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 2, list.get(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3)); + } + + @Test + public void InsertAndMoveMetadataOnePlaceForwardTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // Here we add the first set of metadata to the item + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, one"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, two"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, three"); + + context.restoreAuthSystemState(); + + // The code below performs the mentioned assertions to ensure the place is correct + List list = itemService + .getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + assertThat(list.size(), equalTo(3)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2)); + + context.turnOffAuthorisationSystem(); + + // This is where we add metadata at place=1 + itemService.addAndShiftRightMetadata( + context, item, dcSchema, contributorElement, authorQualifier, null, "test, four", null, -1, 1 + ); + + // Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY) + .stream() + .sorted(Comparator.comparingInt(MetadataValue::getPlace)) + .collect(Collectors.toList()); + assertThat(list.size(), equalTo(4)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 2, list.get(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3)); + + // And move metadata from place=1 to place=2 + itemService.moveMetadata(context, item, dcSchema, contributorElement, authorQualifier, 1, 2); + + context.restoreAuthSystemState(); + + // Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY) + .stream() + .sorted(Comparator.comparingInt(MetadataValue::getPlace)) + .collect(Collectors.toList()); + assertThat(list.size(), equalTo(4)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 2, list.get(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3)); + } + + private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value, + String authority, int place, MetadataValue metadataValue) { + assertThat(metadataValue.getValue(), equalTo(value)); + assertThat(metadataValue.getMetadataField().getMetadataSchema().getName(), equalTo(dcSchema)); + assertThat(metadataValue.getMetadataField().getElement(), equalTo(contributorElement)); + assertThat(metadataValue.getMetadataField().getQualifier(), equalTo(authorQualifier)); + assertThat(metadataValue.getAuthority(), equalTo(authority)); + assertThat(metadataValue.getPlace(), equalTo(place)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java index d1483a6d7a..f238593aba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -165,25 +165,21 @@ public class RelationshipRestRepository extends DSpaceRestRepository dSpaceObjects = utils.constructDSpaceObjectList(context, stringList); if (dSpaceObjects.size() == 1 && dSpaceObjects.get(0).getType() == Constants.ITEM) { - Item replacementItemInRelationship = (Item) dSpaceObjects.get(0); - Item leftItem; - Item rightItem; + Item newLeftItem; + Item newRightItem; + if (itemToReplaceIsRight) { - leftItem = relationship.getLeftItem(); - rightItem = replacementItemInRelationship; + newLeftItem = null; + newRightItem = replacementItemInRelationship; } else { - leftItem = replacementItemInRelationship; - rightItem = relationship.getRightItem(); + newLeftItem = replacementItemInRelationship; + newRightItem = null; } - if (isAllowedToModifyRelationship(context, relationship, leftItem, rightItem)) { - relationship.setLeftItem(leftItem); - relationship.setRightItem(rightItem); - + if (isAllowedToModifyRelationship(context, relationship, newLeftItem, newRightItem)) { try { - relationshipService.updatePlaceInRelationship(context, relationship); - relationshipService.update(context, relationship); + relationshipService.move(context, relationship, newLeftItem, newRightItem); context.commit(); context.reloadEntity(relationship); } catch (AuthorizeException e) { @@ -242,15 +238,17 @@ public class RelationshipRestRepository extends DSpaceRestRepository idRef = new AtomicReference<>(); + AtomicReference idRef2 = new AtomicReference<>(); + try { + String token = getAuthToken(user1.getEmail(), password); + + // Add a relationship @ leftPlace 2 + getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/server/api/core/items/" + publication1 + .getID() + "\n" + + "https://localhost:8080/server/api/core/items/" + author1 + .getID())) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient().perform(get("/api/core/relationships/" + idRef)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(idRef.get()))) + .andExpect(jsonPath("$.leftPlace", is(1))); + + getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/server/api/core/items/" + publication1 + .getID() + "\n" + + "https://localhost:8080/server/api/core/items/" + author2 + .getID())) + .andExpect(status().isCreated()) + .andDo(result -> idRef2.set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient().perform(get("/api/core/relationships/" + idRef2)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(idRef2.get()))) + .andExpect(jsonPath("$.leftPlace", is(2))); + + // Check Item author order + getClient().perform(get("/api/core/items/" + publication1.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata", allOf( + matchMetadata("dc.contributor.author", "Testy, TEst", 0), + matchMetadata("dc.contributor.author", "Smith, Donald", 1), + matchMetadata("dc.contributor.author", "Smith, Maria", 2) + ))); + } finally { + RelationshipBuilder.deleteRelationship(idRef.get()); + if (idRef2.get() != null) { + RelationshipBuilder.deleteRelationship(idRef2.get()); + } + } + } + @Test public void createRelationshipAndAddLeftWardValueAfterwards() throws Exception { context.turnOffAuthorisationSystem(); @@ -2516,6 +2596,104 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest } + @Test + public void putRelationshipWithJsonMoveInFrontOtherMetadata() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + Integer idRef = null; + Integer idRef2 = null; + try { + // Add a relationship + MvcResult mvcResult = getClient(token) + .perform(post("/api/core/relationships") + .param("relationshipType", isAuthorOfPublicationRelationshipType.getID().toString()) + .contentType(MediaType.parseMediaType( + org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/server/api/core/items/" + publication1.getID() + "\n" + + "https://localhost:8080/server/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + idRef = Integer.parseInt(id); + + // Add some more metadata + List ops = new ArrayList(); + ops.add(new AddOperation("/metadata/dc.contributor.author/-", "Metadata, First")); + ops.add(new AddOperation("/metadata/dc.contributor.author/-", "Metadata, Second")); + + getClient(token).perform(patch("/api/core/items/" + publication1.getID()) + .content(getPatchContent(ops)) + .contentType(javax.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)); + + // Add another relationship + mvcResult = getClient(token) + .perform(post("/api/core/relationships") + .param("relationshipType", isAuthorOfPublicationRelationshipType.getID().toString()) + .contentType(MediaType.parseMediaType( + org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/server/api/core/items/" + publication1.getID() + "\n" + + "https://localhost:8080/server/api/core/items/" + author2.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + id = String.valueOf(map.get("id")); + idRef2 = Integer.parseInt(id); + + // Check Item author order + getClient().perform(get("/api/core/items/" + publication1.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata", allOf( + matchMetadata("dc.contributor.author", "Testy, TEst", 0), + matchMetadata("dc.contributor.author", "Smith, Donald", 1), // first relationship + matchMetadata("dc.contributor.author", "Metadata, First", 2), + matchMetadata("dc.contributor.author", "Metadata, Second", 3), + matchMetadata("dc.contributor.author", "Smith, Maria", 4) // second relationship + ))); + + RelationshipRest relationshipRest = new RelationshipRest(); + relationshipRest.setLeftPlace(0); + relationshipRest.setRightPlace(1); + relationshipRest.setLeftwardValue(null); + relationshipRest.setRightwardValue(null); + + // Modify the place of the second relationship -> put it in front of all other metadata + getClient(token).perform(put("/api/core/relationships/" + idRef2) + .contentType(contentType) + .content(mapper.writeValueAsBytes(relationshipRest))) + .andExpect(status().isOk()); + + // Verify the place has changed to the new value + getClient(token).perform(get("/api/core/relationships/" + idRef2)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftPlace", is(0))) + .andExpect(jsonPath("$.rightPlace", is(1))); + + // Verify the other metadata have moved back + getClient().perform(get("/api/core/items/" + publication1.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.metadata", allOf( + matchMetadata("dc.contributor.author", "Smith, Maria", 0), // second relationship + matchMetadata("dc.contributor.author", "Testy, TEst", 1), + matchMetadata("dc.contributor.author", "Smith, Donald", 2), // first relationship + matchMetadata("dc.contributor.author", "Metadata, First", 3), + matchMetadata("dc.contributor.author", "Metadata, Second", 4) + ))); + + } finally { + RelationshipBuilder.deleteRelationship(idRef); + RelationshipBuilder.deleteRelationship(idRef2); + } + + } + @Test public void orgUnitAndOrgUnitRelationshipVirtualMetadataTest() throws Exception { context.turnOffAuthorisationSystem(); From cf107087eccbad2ce62f1b134d32fc6f9a756b6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 00:05:47 +0000 Subject: [PATCH 0691/1254] Bump postgresql from 42.2.24 to 42.2.25 Bumps [postgresql](https://github.com/pgjdbc/pgjdbc) from 42.2.24 to 42.2.25. - [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.2.24...REL42.2.25) --- 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 2999d2ea1f..d3aaee0d76 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.2.2.RELEASE 5.4.10.Final 6.0.18.Final - 42.2.24 + 42.2.25 8.8.1 1.2.22 From 5c5025cd6d1363183f1ace2bc3f1a3d66157dbf2 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 2 Feb 2022 12:08:11 -0500 Subject: [PATCH 0692/1254] Rip out the unused CachingService. $8099 --- dspace-api/pom.xml | 20 +- .../org/dspace/app/util/CacheSnooper.java | 58 -- dspace-iiif/pom.xml | 2 +- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 6 - .../org/dspace/services/CachingService.java | 92 --- .../org/dspace/services/EventService.java | 8 - .../services/caching/CachingServiceImpl.java | 556 ------------------ .../services/caching/model/EhcacheCache.java | 211 ------- .../services/caching/model/MapCache.java | 137 ----- .../services/caching/model/package-info.java | 13 - .../dspace/services/caching/package-info.java | 13 - .../services/events/SystemEventService.java | 98 +-- .../factory/DSpaceServicesFactory.java | 3 - .../factory/DSpaceServicesFactoryImpl.java | 9 - .../main/resources/caching/ehcache-config.xml | 39 -- .../src/main/resources/caching/ehcache.xsd | 265 --------- .../spring/spring-dspace-core-services.xml | 22 - .../services/caching/CachingServiceTest.java | 258 -------- .../services/caching/EhcacheCacheTest.java | 226 ------- .../services/events/EventServiceTest.java | 47 -- .../StatelessRequestServiceImplTest.java | 19 - .../src/test/resources/ehcache-config.xml | 527 ----------------- .../resources/spring/spring-test-beans.xml | 11 - pom.xml | 9 +- 25 files changed, 29 insertions(+), 2622 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/app/util/CacheSnooper.java delete mode 100644 dspace-services/src/main/java/org/dspace/services/CachingService.java delete mode 100644 dspace-services/src/main/java/org/dspace/services/caching/CachingServiceImpl.java delete mode 100644 dspace-services/src/main/java/org/dspace/services/caching/model/EhcacheCache.java delete mode 100644 dspace-services/src/main/java/org/dspace/services/caching/model/MapCache.java delete mode 100644 dspace-services/src/main/java/org/dspace/services/caching/model/package-info.java delete mode 100644 dspace-services/src/main/java/org/dspace/services/caching/package-info.java delete mode 100644 dspace-services/src/main/resources/caching/ehcache-config.xml delete mode 100644 dspace-services/src/main/resources/caching/ehcache.xsd delete mode 100644 dspace-services/src/test/java/org/dspace/services/caching/CachingServiceTest.java delete mode 100644 dspace-services/src/test/java/org/dspace/services/caching/EhcacheCacheTest.java delete mode 100644 dspace-services/src/test/resources/ehcache-config.xml diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index f703f63424..8a81c8574c 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -340,18 +340,23 @@ org.hibernate hibernate-core - - - org.hibernate - hibernate-jcache org.javassist javassist + + + net.bytebuddy + byte-buddy + + + org.hibernate + hibernate-jcache + org.ehcache ehcache @@ -569,13 +574,6 @@ org.mockito mockito-inline test - - - - net.bytebuddy - byte-buddy - - org.springframework diff --git a/dspace-api/src/main/java/org/dspace/app/util/CacheSnooper.java b/dspace-api/src/main/java/org/dspace/app/util/CacheSnooper.java deleted file mode 100644 index 22ad518ea3..0000000000 --- a/dspace-api/src/main/java/org/dspace/app/util/CacheSnooper.java +++ /dev/null @@ -1,58 +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.util; - -import net.sf.ehcache.Cache; -import net.sf.ehcache.CacheManager; -import org.dspace.core.Context; -import org.dspace.servicemanager.DSpaceKernelImpl; -import org.dspace.servicemanager.DSpaceKernelInit; -import org.dspace.services.CachingService; - -/** - * List all EhCache CacheManager and Cache instances. - * - *

    This is a debugging tool, not used in the daily operation of DSpace. - * Just run it from the installed instance using - * {@code bin/dspace dsrun org.dspace.app.util.CacheSnooper} - * to check that the cache configuration is what you expect it to be, - * given your configuration. - * - *

    This was created to prove a specific cache configuration patch, - * but I leave it here in the hope that it may be useful to others. - * - * @author Mark H. Wood - */ -public class CacheSnooper { - private CacheSnooper() { } - - public static void main(String[] argv) { - // Ensure that the DSpace kernel is started. - DSpaceKernelImpl kernel = DSpaceKernelInit.getKernel(null); - - // Ensure that the services cache manager is started. - CachingService serviceCaches = kernel.getServiceManager() - .getServiceByName(null, CachingService.class); - - // Ensure that the database layer is started. - Context ctx = new Context(); - - // List those caches! - for (CacheManager manager : CacheManager.ALL_CACHE_MANAGERS) { - System.out.format("CacheManager: %s%n", manager); - for (String cacheName : manager.getCacheNames()) { - Cache cache = manager.getCache(cacheName); - System.out.format(" Cache: '%s'; maxHeap: %d; maxDisk: %d%n", - cacheName, - cache.getCacheConfiguration().getMaxEntriesLocalHeap(), - cache.getCacheConfiguration().getMaxEntriesLocalDisk()); - } - } - } -} diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index a0e123dcd0..cfff936687 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -72,7 +72,7 @@ org.ehcache ehcache - 3.4.0 + ${ehcache.version} diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 24959edd93..fdf826d735 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -505,7 +505,7 @@ org.ehcache ehcache - 3.4.0 + ${ehcache.version} org.apache.solr diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 2fdb60a1c4..18bcbdf980 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -90,12 +90,6 @@ org.apache.commons commons-lang3 - - org.ehcache - ehcache - ${ehcache.version} - compile - javax.servlet diff --git a/dspace-services/src/main/java/org/dspace/services/CachingService.java b/dspace-services/src/main/java/org/dspace/services/CachingService.java deleted file mode 100644 index 0672a1b2b7..0000000000 --- a/dspace-services/src/main/java/org/dspace/services/CachingService.java +++ /dev/null @@ -1,92 +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.services; - -import java.util.List; - -import org.dspace.services.model.Cache; -import org.dspace.services.model.CacheConfig; - -/** - * A service to manage creation and retrieval of caches. - * - * @author Aaron Zeckoski (azeckoski @ gmail.com) - */ -public interface CachingService { - - /** - * This is the cache key used to stored requests in a request cache, - * typically handled by a servlet filter but can be handled by - * anything. - * This is here to ensure we use the right name. - */ - public static final String REQUEST_CACHE = "dsRequestCache"; - - /** - * Gets all the caches that the service knows about. - * This will include caches of all scopes but only includes request - * caches for the current thread. - * - * @return a list of all the caches which the caching service knows about - */ - public List getCaches(); - - /** - * Construct a Cache with the given name OR retrieve the one that - * already exists with this name. Often the name is the fully - * qualified classpath of the API for the service that is being - * cached, or of the class if there is no API. - * This will operate on system defaults (probably a distributed - * cache without replication) OR it will use the defaults which are - * configured for this cacheName (part of the underlying - * implementation) if the cacheConfig is null. - *

    - * This can only retrieve request caches for the current request. - *

    - * If the cache already exists then the cacheConfig is ignored. - * - * @param cacheName the unique name for this cache (e.g. org.dspace.user.UserCache) - * @param cacheConfig defines the configuration for this cache - * @return a cache which can be used to store objects - */ - public Cache getCache(String cacheName, CacheConfig cacheConfig); - - /** - * Flushes and destroys the cache with this name. - * Generally there is no reason to call this. - * - * @param cacheName the unique name for this cache (e.g. org.dspace.user.UserCache) - */ - public void destroyCache(String cacheName); - - /** - * Get a status report of cache usage which is suitable for log or - * screen output. - * - * @param cacheName (optional) the unique name for this cache (e.g. org.dspace.user.UserCache) - * OR null for status of all caches - * @return a string representing the current status of the specified cache or all caches - */ - public String getStatus(String cacheName); - - /** - * Clears all caches. - * Generally there is no reason to call this. - * - * @throws SecurityException if the current user does not have super user permissions - */ - public void resetCaches(); - - /** - * Unbinds all request caches. Destroys the caches completely. - * You should not call this unless you know what you are doing; - * it is handled automatically by the system. - */ - public void unbindRequestCaches(); - -} diff --git a/dspace-services/src/main/java/org/dspace/services/EventService.java b/dspace-services/src/main/java/org/dspace/services/EventService.java index 4944389b05..92080f0358 100644 --- a/dspace-services/src/main/java/org/dspace/services/EventService.java +++ b/dspace-services/src/main/java/org/dspace/services/EventService.java @@ -27,14 +27,6 @@ public interface EventService { */ public void fireEvent(Event event); - /** - * Queues up an event to be fired at the end of a successful - * request/transaction. - * - * @param event contains the data related to this event - */ - public void queueEvent(Event event); - /** * Register an event listener which will be notified when events occur. * diff --git a/dspace-services/src/main/java/org/dspace/services/caching/CachingServiceImpl.java b/dspace-services/src/main/java/org/dspace/services/caching/CachingServiceImpl.java deleted file mode 100644 index cd803b4df1..0000000000 --- a/dspace-services/src/main/java/org/dspace/services/caching/CachingServiceImpl.java +++ /dev/null @@ -1,556 +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.services.caching; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -import org.dspace.kernel.ServiceManager; -import org.dspace.kernel.mixins.ConfigChangeListener; -import org.dspace.services.CachingService; -import org.dspace.services.ConfigurationService; -import org.dspace.services.RequestService; -import org.dspace.services.caching.model.EhcacheCache; -import org.dspace.services.caching.model.MapCache; -import org.dspace.services.model.Cache; -import org.dspace.services.model.CacheConfig; -import org.dspace.services.model.CacheConfig.CacheScope; -import org.dspace.services.model.RequestInterceptor; -import org.ehcache.core.Ehcache; -import org.ehcache.Statistics; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * Implementation of the core caching service, which is available for - * anyone who is writing code for DSpace to use. - * - * @author Aaron Zeckoski (azeckoski @ gmail.com) - */ -public final class CachingServiceImpl - implements CachingService, ConfigChangeListener { - - private static final Logger log = LoggerFactory.getLogger(CachingServiceImpl.class); - - /** - * This is the event key for a full cache reset. - */ - protected static final String EVENT_RESET = "caching.reset"; - /** - * The default configuration location. - */ - protected static final String DEFAULT_CONFIG = "org/dspace/services/caching/ehcache-config.xml"; - - /** - * All the non-thread caches that we know about. - * Mostly used for tracking purposes. - */ - private final Map cacheRecord = new ConcurrentHashMap<>(); - - /** - * All the request caches. This is bound to the thread. - * The initial value of this TL is set automatically when it is - * created. - */ - private final Map> requestCachesMap = new ConcurrentHashMap<>(); - - /** - * @return the current request map which is bound to the current thread - */ - protected Map getRequestCaches() { - if (requestService == null || requestService.getCurrentRequestId() == null) { - return null; - } - - Map requestCaches = requestCachesMap.get(requestService.getCurrentRequestId()); - if (requestCaches == null) { - requestCaches = new HashMap<>(); - requestCachesMap.put(requestService.getCurrentRequestId(), requestCaches); - } - - return requestCaches; - } - - /** - * Unbinds all request caches. Destroys the caches completely. - */ - @Override - public void unbindRequestCaches() { - if (requestService != null) { - requestCachesMap.remove(requestService.getCurrentRequestId()); - } - } - - private ConfigurationService configurationService; - - @Autowired(required = true) - public void setConfigurationService(ConfigurationService configurationService) { - this.configurationService = configurationService; - } - - private RequestService requestService; - - @Autowired - public void setRequestService(RequestService requestService) { - this.requestService = requestService; - } - - private ServiceManager serviceManager; - - @Autowired(required = true) - public void setServiceManager(ServiceManager serviceManager) { - this.serviceManager = serviceManager; - } - - /** - * The underlying cache manager; injected. - */ - protected org.ehcache.CacheManager cacheManager; - - @Autowired(required = true) - public void setCacheManager(org.ehcache.CacheManager cacheManager) { - this.cacheManager = cacheManager; - } - - public org.ehcache.CacheManager getCacheManager() { - return cacheManager; - } - - private boolean useClustering = false; - private boolean useDiskStore = true; - private int maxElementsInMemory = 2000; - private int timeToLiveSecs = 3600; - private int timeToIdleSecs = 600; - - /** - * Reloads the configuration settings from the configuration service. - */ - protected void reloadConfig() { - // Reload caching configurations, but have sane default values if unspecified in configs - useClustering = configurationService.getPropertyAsType(knownConfigNames[0], false); - useDiskStore = configurationService.getPropertyAsType(knownConfigNames[1], true); - maxElementsInMemory = configurationService.getPropertyAsType(knownConfigNames[2], 2000); - timeToLiveSecs = configurationService.getPropertyAsType(knownConfigNames[3], 3600); - timeToIdleSecs = configurationService.getPropertyAsType(knownConfigNames[4], 600); - } - - /** - * WARNING: Do not change the order of these!
    - * If you do, you have to fix the {@link #reloadConfig()} method -AZ - */ - private final String[] knownConfigNames = { - "caching.use.clustering", // bool - whether to use clustering - "caching.default.use.disk.store", // whether to use the disk store - "caching.default.max.elements", // the maximum number of elements in memory, before they are evicted - "caching.default.time.to.live.secs", // the default amount of time to live for an element from its creation date - "caching.default.time.to.idle.secs", // the default amount of time to live for an element from its last - // accessed or modified date - }; - - /* (non-Javadoc) - * @see org.dspace.kernel.mixins.ConfigChangeListener#notifyForConfigNames() - */ - @Override - public String[] notifyForConfigNames() { - return knownConfigNames.clone(); - } - - /* (non-Javadoc) - * @see org.dspace.kernel.mixins.ConfigChangeListener#configurationChanged(java.util.List, java.util.Map) - */ - @Override - public void configurationChanged(List changedSettingNames, Map changedSettings) { - reloadConfig(); - } - - @PostConstruct - public void init() { - log.info("init()"); - // get settings - reloadConfig(); - - // get all caches out of the cachemanager and load them into the cache list - List ehcaches = getAllEhCaches(false); - for (Ehcache ehcache : ehcaches) { - EhcacheCache cache = new EhcacheCache(ehcache, null); - cacheRecord.put(cache.getName(), cache); - } - - if (requestService != null) { - requestService.registerRequestInterceptor(new CachingServiceRequestInterceptor()); - } - - log.info("Caching service initialized:\n" + getStatus(null)); - } - - @PreDestroy - public void shutdown() { - log.info("destroy()"); - // for some reason this causes lots of errors so not using it for now -AZ - //ehCacheManagementService.dispose(); - try { - cacheRecord.clear(); - } catch (RuntimeException e) { - // whatever - } - try { - requestCachesMap.clear(); - } catch (RuntimeException e) { - // whatever - } - try { - cacheManager.removalAll(); - } catch (RuntimeException e) { - // whatever - } - try { - cacheManager.shutdown(); - } catch (RuntimeException e) { - // whatever - } - } - - /* (non-Javadoc) - * @see org.dspace.services.CachingService#destroyCache(java.lang.String) - */ - @Override - public void destroyCache(String cacheName) { - if (cacheName == null || "".equals(cacheName)) { - throw new IllegalArgumentException("cacheName cannot be null or empty string"); - } - - EhcacheCache cache = cacheRecord.get(cacheName); - if (cache != null) { - cacheManager.removeCache(cacheName); - cacheRecord.remove(cacheName); - } else { - Map caches = getRequestCaches(); - if (caches != null) { - caches.remove(cacheName); - } - } - } - - /* (non-Javadoc) - * @see org.dspace.services.CachingService#getCache(java.lang.String, org.dspace.services.model.CacheConfig) - */ - @Override - public Cache getCache(String cacheName, CacheConfig cacheConfig) { - Cache cache = null; - - if (cacheName == null || "".equals(cacheName)) { - throw new IllegalArgumentException("cacheName cannot be null or empty string"); - } - - if (cacheConfig != null && CacheScope.REQUEST.equals(cacheConfig.getCacheScope())) { - Map caches = getRequestCaches(); - if (caches != null) { - cache = caches.get(cacheName); - } - - if (cache == null) { - cache = instantiateMapCache(cacheName, cacheConfig); - } - } else { - // find the cache in the records if possible - cache = this.cacheRecord.get(cacheName); - - if (cache == null) { - cache = instantiateEhCache(cacheName, cacheConfig); - } - } - - return cache; - } - - /* (non-Javadoc) - * @see org.dspace.services.CachingService#getCaches() - */ - @Override - public List getCaches() { - List caches = new ArrayList<>(this.cacheRecord.values()); -// TODO implement reporting on request caches? -// caches.addAll(this.requestMap.values()); - Collections.sort(caches, new NameComparator()); - return caches; - } - - /* (non-Javadoc) - * @see org.dspace.services.CachingService#getStatus(java.lang.String) - */ - @Override - public String getStatus(String cacheName) { - final StringBuilder sb = new StringBuilder(); - - if (cacheName == null || "".equals(cacheName)) { - // add in overall memory stats - sb.append("** Memory report\n"); - sb.append(" freeMemory: ").append(Runtime.getRuntime().freeMemory()); - sb.append("\n"); - sb.append(" totalMemory: ").append(Runtime.getRuntime().totalMemory()); - sb.append("\n"); - sb.append(" maxMemory: ").append(Runtime.getRuntime().maxMemory()); - sb.append("\n"); - - // caches summary report - List allCaches = getCaches(); - sb.append("\n** Full report of all known caches (").append(allCaches.size()).append("):\n"); - for (Cache cache : allCaches) { - sb.append(" * "); - sb.append(cache.toString()); - sb.append("\n"); - if (cache instanceof EhcacheCache) { - Ehcache ehcache = ((EhcacheCache) cache).getCache(); - sb.append(generateCacheStats(ehcache)); - sb.append("\n"); - } - } - } else { - // report for a single cache - sb.append("\n** Report for cache (").append(cacheName).append("):\n"); - Cache cache = this.cacheRecord.get(cacheName); - if (cache == null) { - Map caches = getRequestCaches(); - if (caches != null) { - cache = caches.get(cacheName); - } - } - if (cache == null) { - sb.append(" * Could not find cache by this name: ").append(cacheName); - } else { - sb.append(" * "); - sb.append(cache.toString()); - sb.append("\n"); - if (cache instanceof EhcacheCache) { - Ehcache ehcache = ((EhcacheCache) cache).getCache(); - sb.append(generateCacheStats(ehcache)); - sb.append("\n"); - } - } - } - - return sb.toString(); - } - - /* (non-Javadoc) - * @see org.dspace.services.CachingService#resetCaches() - */ - @Override - public void resetCaches() { - log.debug("resetCaches()"); - - List allCaches = getCaches(); - for (Cache cache : allCaches) { - cache.clear(); - } - - System.runFinalization(); // force the JVM to try to clean up any remaining objects - // DO NOT CALL System.gc() here or I will have you shot -AZ - - log.info("doReset(): Memory Recovery to: " + Runtime.getRuntime().freeMemory()); - } - - /** - * Return all caches from the CacheManager. - * - * @param sorted if true then sort by name - * @return the list of all known ehcaches - */ - protected List getAllEhCaches(boolean sorted) { - log.debug("getAllCaches()"); - - final String[] cacheNames = cacheManager.getCacheNames(); - if (sorted) { - Arrays.sort(cacheNames); - } - final List caches = new ArrayList<>(cacheNames.length); - for (String cacheName : cacheNames) { - caches.add(cacheManager.getEhcache(cacheName)); - } - return caches; - } - - /** - * Create an EhcacheCache (and the associated EhCache) using the - * supplied name (with default settings), or get the cache out of - * Spring or the current configured cache. - *

    - * This expects that the cacheRecord has already been checked and - * will not check it again. - *

    - * Will proceed in this order: - *

      - *
    1. Attempt to load a bean with the name of the cache
    2. - *
    3. Attempt to load cache from caching system
    4. - *
    5. Create a new cache by this name
    6. - *
    7. Put the cache in the cache record
    8. - *
    - * - * @param cacheName the name of the cache - * @param cacheConfig the config for this cache - * @return a cache instance - */ - protected EhcacheCache instantiateEhCache(String cacheName, CacheConfig cacheConfig) { - if (log.isDebugEnabled()) { - log.debug("instantiateEhCache(String " + cacheName + ")"); - } - - if (cacheName == null || "".equals(cacheName)) { - throw new IllegalArgumentException("String cacheName must not be null or empty!"); - } - - // try to locate a named cache in the service manager - Ehcache ehcache = serviceManager.getServiceByName(cacheName, Ehcache.class); - - // try to locate the cache in the cacheManager by name - if (ehcache == null) { - // if this cache name is created or already in use then we just get it - if (!cacheManager.cacheExists(cacheName)) { - // did not find the cache - if (cacheConfig == null) { - cacheManager.addCache(cacheName); // create a new cache using ehcache defaults - } else { - if (useClustering) { - // TODO - throw new UnsupportedOperationException("Still need to do this"); - } else { - cacheManager.addCache(cacheName); // create a new cache using ehcache defaults - } - } - log.info("Created new Cache (from default settings): " + cacheName); - } - ehcache = cacheManager.getEhcache(cacheName); - } - // wrap the ehcache in the cache impl - EhcacheCache cache = new EhcacheCache(ehcache, cacheConfig); - cacheRecord.put(cacheName, cache); - return cache; - } - - /** - * Create a thread map cache using the supplied name with supplied - * settings. - *

    - * This expects that the cacheRecord has already been checked and - * will not check it again. It also places the cache into the - * request map. - * - * @param cacheName the name of the cache - * @param cacheConfig the config for this cache - * @return a cache instance - */ - protected MapCache instantiateMapCache(String cacheName, CacheConfig cacheConfig) { - if (log.isDebugEnabled()) { - log.debug("instantiateMapCache(String " + cacheName + ")"); - } - - if (cacheName == null || "".equals(cacheName)) { - throw new IllegalArgumentException("String cacheName must not be null or empty!"); - } - - // check for existing cache - MapCache cache = null; - CacheScope scope = CacheScope.REQUEST; - if (cacheConfig != null) { - scope = cacheConfig.getCacheScope(); - } - - Map caches = getRequestCaches(); - if (caches != null) { - if (CacheScope.REQUEST.equals(scope)) { - cache = caches.get(cacheName); - } - - if (cache == null) { - cache = new MapCache(cacheName, cacheConfig); - // place cache into the right TL - if (CacheScope.REQUEST.equals(scope)) { - caches.put(cacheName, cache); - } - } - } - - return cache; - } - - /** - * Generate some stats for this cache. - * Note that this is not cheap so do not use it very often. - * - * @param cache an Ehcache - * @return the stats of this cache as a string - */ - protected static String generateCacheStats(Ehcache cache) { - StringBuilder sb = new StringBuilder(); - sb.append(cache.getName()).append(":"); - // this will make this costly but it is important to get accurate settings - cache.setStatisticsAccuracy(Statistics.STATISTICS_ACCURACY_GUARANTEED); - Statistics stats = cache.getStatistics(); - final long memSize = cache.getMemoryStoreSize(); - final long diskSize = cache.getDiskStoreSize(); - final long size = memSize + diskSize; - final long hits = stats.getCacheHits(); - final long misses = stats.getCacheMisses(); - final String hitPercentage = ((hits + misses) > 0) ? ((100l * hits) / (hits + misses)) + "%" : "N/A"; - final String missPercentage = ((hits + misses) > 0) ? ((100l * misses) / (hits + misses)) + "%" : "N/A"; - sb.append(" Size: ").append(size).append(" [memory:").append(memSize).append(", disk:").append(diskSize) - .append("]"); - sb.append(", Hits: ").append(hits).append(" [memory:").append(stats.getInMemoryHits()).append(", disk:") - .append(stats.getOnDiskHits()).append("] (").append(hitPercentage).append(")"); - sb.append(", Misses: ").append(misses).append(" (").append(missPercentage).append(")"); - return sb.toString(); - } - - /** - * Compare two Cache objects by name. - */ - public static final class NameComparator implements Comparator, Serializable { - public static final long serialVersionUID = 1l; - - @Override - public int compare(Cache o1, Cache o2) { - return o1.getName().compareTo(o2.getName()); - } - } - - private class CachingServiceRequestInterceptor implements RequestInterceptor { - - @Override - public void onStart(String requestId) { - if (requestId != null) { - Map requestCaches = requestCachesMap.get(requestId); - if (requestCaches == null) { - requestCaches = new HashMap<>(); - requestCachesMap.put(requestId, requestCaches); - } - } - } - - @Override - public void onEnd(String requestId, boolean succeeded, Exception failure) { - if (requestId != null) { - requestCachesMap.remove(requestId); - } - } - - @Override - public int getOrder() { - return 1; - } - } -} diff --git a/dspace-services/src/main/java/org/dspace/services/caching/model/EhcacheCache.java b/dspace-services/src/main/java/org/dspace/services/caching/model/EhcacheCache.java deleted file mode 100644 index 5f9b16791c..0000000000 --- a/dspace-services/src/main/java/org/dspace/services/caching/model/EhcacheCache.java +++ /dev/null @@ -1,211 +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.services.caching.model; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -import org.dspace.services.model.Cache; -import org.dspace.services.model.CacheConfig; -import org.dspace.services.model.CacheConfig.CacheScope; -import org.ehcache.core.Ehcache; -import org.ehcache.Element; -import org.ehcache.Status; - - -/** - * The ehcache implementation of the Cache object. - * - * @author Aaron Zeckoski (azeckoski @ gmail.com) - */ -public final class EhcacheCache implements Cache { - - protected Ehcache cache; - - public Ehcache getCache() { - return cache; - } - - protected CacheConfig cacheConfig; - - public EhcacheCache(Ehcache cache, CacheConfig cacheConfig) { - // setup the cache - if (cache == null) { - throw new NullPointerException("Cache must be set and cannot be null"); - } else { - if (cache.getStatus() != Status.STATUS_ALIVE) { - throw new IllegalArgumentException( - "Cache (" + cache.getName() + ") must already be initialized and alive"); - } - } - this.cache = cache; - if (cacheConfig != null) { - this.cacheConfig = cacheConfig; - } else { - this.cacheConfig = new CacheConfig(CacheScope.INSTANCE); - } - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#getConfig() - */ - public CacheConfig getConfig() { - return cacheConfig; - } - - /* (non-Javadoc) - * @see org.sakaiproject.caching.Cache#clear() - */ - public void clear() { - cache.removeAll(); - cache.clearStatistics(); - } - - /* (non-Javadoc) - * @see org.sakaiproject.caching.Cache#exists(java.lang.String) - */ - public boolean exists(String key) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - return cache.isKeyInCache(key); - } - - /* (non-Javadoc) - * @see org.sakaiproject.caching.Cache#get(java.lang.String) - */ - public Object get(String key) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - - return (Serializable) getCachePayload(key, false); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#getKeys() - */ - public List getKeys() { - ArrayList keys = new ArrayList(); - List eKeys = cache.getKeys(); - for (Object object : eKeys) { - if (object != null) { - keys.add(object.toString()); - } - } - return keys; - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#look(java.lang.String) - */ - public Object look(String key) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - - return (Serializable) getCachePayload(key, true); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#put(java.lang.String, java.io.Serializable) - */ - public void put(String key, Object value) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - cache.put(new Element(key, value)); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#getName() - */ - public String getName() { - if (cache != null) { - return cache.getName(); - } else { - return "NULL cache"; - } - } - - /* (non-Javadoc) - * @see org.sakaiproject.caching.Cache#remove(java.lang.String) - */ - public boolean remove(String key) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - return cache.remove(key); - } - - /* (non-Javadoc) - * @see org.sakaiproject.caching.Cache#size() - */ - public int size() { - return cache.getSize(); - } - - /** - * Retrieve a payload from the cache for this key if one can be found. - * - * @param key the key for this cache element - * @return the payload or null if none found - */ - private Object getCachePayload(String key, boolean quiet) { - Object payload = null; - Element e; - if (quiet) { - e = cache.getQuiet(key); - } else { - e = cache.get(key); - } - if (e != null) { - // attempt to get the serialized value first - if (e.isSerializable()) { - payload = e.getValue(); - } else { - // not serializable so get the object value - payload = e.getObjectValue(); - } - } - return payload; - } - - @Override - public boolean equals(Object obj) { - if (null == obj) { - return false; - } - if (!(obj instanceof EhcacheCache)) { - return false; - } else { - EhcacheCache castObj = (EhcacheCache) obj; - if (null == this.getName() || null == castObj.getName()) { - return false; - } else { - return (this.getName().equals(castObj.getName())); - } - } - } - - @Override - public int hashCode() { - if (null == this.getName()) { - return super.hashCode(); - } - String hashStr = this.getClass().getName() + ":" + this.getName().hashCode(); - return hashStr.hashCode(); - } - - @Override - public String toString() { - return "EhCache:name=" + getName() + ":Scope=" + cacheConfig.getCacheScope() + ":size=" + size(); - } - -} diff --git a/dspace-services/src/main/java/org/dspace/services/caching/model/MapCache.java b/dspace-services/src/main/java/org/dspace/services/caching/model/MapCache.java deleted file mode 100644 index 49e3aead0d..0000000000 --- a/dspace-services/src/main/java/org/dspace/services/caching/model/MapCache.java +++ /dev/null @@ -1,137 +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.services.caching.model; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.dspace.services.model.Cache; -import org.dspace.services.model.CacheConfig; -import org.dspace.services.model.CacheConfig.CacheScope; - - -/** - * This is a simple Cache that just uses a map to store the cache values. - * Used for the request and thread caches. - * - * @author Aaron Zeckoski (azeckoski @ gmail.com) - */ -public final class MapCache implements Cache { - - private Map cache; - - public Map getCache() { - return cache; - } - - protected String name; - protected CacheConfig cacheConfig; - - public MapCache(String name, CacheConfig cacheConfig) { - if (name == null) { - throw new IllegalArgumentException("name cannot be null"); - } - this.name = name; - this.cache = new HashMap(); - if (cacheConfig != null) { - this.cacheConfig = cacheConfig; - } else { - this.cacheConfig = new CacheConfig(CacheScope.REQUEST); - } - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#clear() - */ - public void clear() { - this.cache.clear(); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#exists(java.lang.String) - */ - public boolean exists(String key) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - return this.cache.containsKey(key); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#get(java.lang.String) - */ - public Object get(String key) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - return this.cache.get(key); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#getKeys() - */ - public List getKeys() { - return new ArrayList(this.cache.keySet()); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#getConfig() - */ - public CacheConfig getConfig() { - return this.cacheConfig; - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#getName() - */ - public String getName() { - return this.name; - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#look(java.lang.String) - */ - public Object look(String key) { - return get(key); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#put(java.lang.String, java.io.Serializable) - */ - public void put(String key, Object value) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - this.cache.put(key, value); - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#remove(java.lang.String) - */ - public boolean remove(String key) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - return this.cache.remove(key) != null; - } - - /* (non-Javadoc) - * @see org.dspace.services.model.Cache#size() - */ - public int size() { - return this.cache.size(); - } - - @Override - public String toString() { - return "MapCache:name=" + getName() + ":Scope=" + cacheConfig.getCacheScope() + ":size=" + size(); - } - -} diff --git a/dspace-services/src/main/java/org/dspace/services/caching/model/package-info.java b/dspace-services/src/main/java/org/dspace/services/caching/model/package-info.java deleted file mode 100644 index 67e1ee1ffc..0000000000 --- a/dspace-services/src/main/java/org/dspace/services/caching/model/package-info.java +++ /dev/null @@ -1,13 +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/ - */ - -/** - * Implementations of the Cache type, for various purposes. - */ - -package org.dspace.services.caching.model; diff --git a/dspace-services/src/main/java/org/dspace/services/caching/package-info.java b/dspace-services/src/main/java/org/dspace/services/caching/package-info.java deleted file mode 100644 index a73e0d3eaa..0000000000 --- a/dspace-services/src/main/java/org/dspace/services/caching/package-info.java +++ /dev/null @@ -1,13 +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/ - */ - -/** - * Implementation of the core Caching service. - */ - -package org.dspace.services.caching; diff --git a/dspace-services/src/main/java/org/dspace/services/events/SystemEventService.java b/dspace-services/src/main/java/org/dspace/services/events/SystemEventService.java index 39a1f41f6a..1787c688f6 100644 --- a/dspace-services/src/main/java/org/dspace/services/events/SystemEventService.java +++ b/dspace-services/src/main/java/org/dspace/services/events/SystemEventService.java @@ -7,20 +7,14 @@ */ package org.dspace.services.events; -import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PreDestroy; import org.apache.commons.lang3.ArrayUtils; -import org.dspace.services.CachingService; import org.dspace.services.EventService; import org.dspace.services.RequestService; -import org.dspace.services.model.Cache; -import org.dspace.services.model.CacheConfig; -import org.dspace.services.model.CacheConfig.CacheScope; import org.dspace.services.model.Event; import org.dspace.services.model.Event.Scope; import org.dspace.services.model.EventListener; @@ -40,24 +34,20 @@ public final class SystemEventService implements EventService { private final Logger log = LoggerFactory.getLogger(SystemEventService.class); - private static final String QUEUE_CACHE_NAME = "eventQueueCache"; - /** * Map for holding onto the listeners which is ClassLoader safe. */ private final Map listenersMap = new ConcurrentHashMap<>(); private final RequestService requestService; - private final CachingService cachingService; private EventRequestInterceptor requestInterceptor; @Autowired(required = true) - public SystemEventService(RequestService requestService, CachingService cachingService) { - if (requestService == null || cachingService == null) { - throw new IllegalArgumentException("requestService, cachingService, and all inputs must not be null"); + public SystemEventService(RequestService requestService) { + if (requestService == null) { + throw new IllegalArgumentException("requestService and all inputs must not be null"); } this.requestService = requestService; - this.cachingService = cachingService; // register interceptor this.requestInterceptor = new EventRequestInterceptor(); @@ -93,28 +83,6 @@ public final class SystemEventService implements EventService { } } - /* (non-Javadoc) - * @see org.dspace.services.EventService#queueEvent(org.dspace.services.model.Event) - */ - @Override - public void queueEvent(Event event) { - validateEvent(event); - - // get the cache - Cache queueCache = this.cachingService.getCache(QUEUE_CACHE_NAME, new CacheConfig(CacheScope.REQUEST)); - - // put the event in the queue if this is in a request - if (requestService.getCurrentRequestId() != null) { - // create a key which is orderable and unique - String key = System.currentTimeMillis() + ":" + queueCache.size() + ":" + event.getId(); - queueCache.put(key, event); - } else { - // no request so fire the event immediately - log.info("No request to queue this event (" + event + ") so firing immediately"); - fireEvent(event); - } - } - /* (non-Javadoc) * @see org.dspace.services.EventService#registerEventListener(org.dspace.services.model.EventListener) */ @@ -143,10 +111,9 @@ public final class SystemEventService implements EventService { try { listener.receiveEvent(event); } catch (Exception e) { - log.warn("Listener (" + listener + ")[" + listener.getClass() - .getName() + "] failed to recieve event (" + - event + "): " + e - .getMessage() + ":" + e.getCause()); + log.warn("Listener ({})[{}] failed to receive event ({}): {}:{}", + listener, listener.getClass().getName(), event, + e.getMessage(), e.getCause()); } } } @@ -176,40 +143,6 @@ public final class SystemEventService implements EventService { "external listeners: " + event); } - /** - * Fires all queued events for the current request. - * - * @return the number of events which were fired - */ - protected int fireQueuedEvents() { - int fired = 0; - Cache queueCache = this.cachingService.getCache(QUEUE_CACHE_NAME, new CacheConfig(CacheScope.REQUEST)); - - List eventIds = queueCache.getKeys(); - Collections.sort(eventIds); // put it in the order they were added (hopefully) - if (eventIds.size() > 0) { - for (String eventId : eventIds) { - Event event = (Event) queueCache.get(eventId); - fireEvent(event); - fired++; - } - } - queueCache.clear(); - return fired; - } - - /** - * Clears all events for the current request. - * - * @return the number of events that were cleared - */ - protected int clearQueuedEvents() { - Cache queueCache = this.cachingService.getCache(QUEUE_CACHE_NAME, new CacheConfig(CacheScope.REQUEST)); - int cleared = queueCache.size(); - queueCache.clear(); - return cleared; - } - /** * This will validate the event object and set any values which are * unset but can be figured out. @@ -265,10 +198,8 @@ public final class SystemEventService implements EventService { } } } catch (Exception e1) { - log.warn("Listener (" + listener + ")[" + listener.getClass() - .getName() + "] failure calling getEventNamePrefixes: " - + e1 - .getMessage() + ":" + e1.getCause()); + log.warn("Listener ({})[{}] failure calling getEventNamePrefixes: {}:{}", + listener, listener.getClass().getName(), e1.getMessage(), e1.getCause()); } boolean allowResource = true; try { @@ -286,9 +217,8 @@ public final class SystemEventService implements EventService { } } } catch (Exception e1) { - log.warn("Listener (" + listener + ")[" + listener.getClass() - .getName() + "] failure calling getResourcePrefix: " + e1 - .getMessage() + ":" + e1.getCause()); + log.warn("Listener ({})[{}] failure calling getResourcePrefix: {}:{}", + listener, listener.getClass().getName(), e1.getMessage(), e1.getCause()); } return allowName && allowResource; @@ -328,14 +258,6 @@ public final class SystemEventService implements EventService { */ @Override public void onEnd(String requestId, boolean succeeded, Exception failure) { - if (succeeded) { - int fired = fireQueuedEvents(); - log.debug("Fired " + fired + " events at the end of the request (" + requestId + ")"); - } else { - int cleared = clearQueuedEvents(); - log.debug( - "Cleared/cancelled " + cleared + " events at the end of the failed request (" + requestId + ")"); - } } /* (non-Javadoc) diff --git a/dspace-services/src/main/java/org/dspace/services/factory/DSpaceServicesFactory.java b/dspace-services/src/main/java/org/dspace/services/factory/DSpaceServicesFactory.java index 0e91388b7d..eec5a69a17 100644 --- a/dspace-services/src/main/java/org/dspace/services/factory/DSpaceServicesFactory.java +++ b/dspace-services/src/main/java/org/dspace/services/factory/DSpaceServicesFactory.java @@ -8,7 +8,6 @@ package org.dspace.services.factory; import org.dspace.kernel.ServiceManager; -import org.dspace.services.CachingService; import org.dspace.services.ConfigurationService; import org.dspace.services.EmailService; import org.dspace.services.EventService; @@ -20,8 +19,6 @@ import org.dspace.utils.DSpace; * implementation */ public abstract class DSpaceServicesFactory { - public abstract CachingService getCachingService(); - public abstract ConfigurationService getConfigurationService(); public abstract EmailService getEmailService(); diff --git a/dspace-services/src/main/java/org/dspace/services/factory/DSpaceServicesFactoryImpl.java b/dspace-services/src/main/java/org/dspace/services/factory/DSpaceServicesFactoryImpl.java index 328f49bfc1..9e3fa0ae4e 100644 --- a/dspace-services/src/main/java/org/dspace/services/factory/DSpaceServicesFactoryImpl.java +++ b/dspace-services/src/main/java/org/dspace/services/factory/DSpaceServicesFactoryImpl.java @@ -8,7 +8,6 @@ package org.dspace.services.factory; import org.dspace.kernel.ServiceManager; -import org.dspace.services.CachingService; import org.dspace.services.ConfigurationService; import org.dspace.services.EmailService; import org.dspace.services.EventService; @@ -21,9 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class DSpaceServicesFactoryImpl extends DSpaceServicesFactory { - @Autowired(required = true) - private CachingService cachingService; - @Autowired(required = true) private ConfigurationService configurationService; @@ -39,11 +35,6 @@ public class DSpaceServicesFactoryImpl extends DSpaceServicesFactory { @Autowired(required = true) private ServiceManager serviceManager; - @Override - public CachingService getCachingService() { - return cachingService; - } - @Override public ConfigurationService getConfigurationService() { return configurationService; diff --git a/dspace-services/src/main/resources/caching/ehcache-config.xml b/dspace-services/src/main/resources/caching/ehcache-config.xml deleted file mode 100644 index bd32b38740..0000000000 --- a/dspace-services/src/main/resources/caching/ehcache-config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - diff --git a/dspace-services/src/main/resources/caching/ehcache.xsd b/dspace-services/src/main/resources/caching/ehcache.xsd deleted file mode 100644 index 349734c184..0000000000 --- a/dspace-services/src/main/resources/caching/ehcache.xsd +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-services/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-services/src/main/resources/spring/spring-dspace-core-services.xml index fc769bd7fd..04c2576d4b 100644 --- a/dspace-services/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-services/src/main/resources/spring/spring-dspace-core-services.xml @@ -32,28 +32,6 @@ - - - - - - - - - - ehcache-config.xml - caching/ehcache-config.xml - - - - - - - - - diff --git a/dspace-services/src/test/java/org/dspace/services/caching/CachingServiceTest.java b/dspace-services/src/test/java/org/dspace/services/caching/CachingServiceTest.java deleted file mode 100644 index 7236a04f58..0000000000 --- a/dspace-services/src/test/java/org/dspace/services/caching/CachingServiceTest.java +++ /dev/null @@ -1,258 +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.services.caching; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; - -import java.util.List; - -import org.dspace.services.RequestService; -import org.dspace.services.caching.model.EhcacheCache; -import org.dspace.services.caching.model.MapCache; -import org.dspace.services.model.Cache; -import org.dspace.services.model.CacheConfig; -import org.dspace.services.model.CacheConfig.CacheScope; -import org.dspace.test.DSpaceAbstractKernelTest; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - - -/** - * Testing the caching service - * - * @author Aaron Zeckoski (azeckoski @ gmail.com) - */ -public class CachingServiceTest extends DSpaceAbstractKernelTest { - - private CachingServiceImpl cachingService; - private RequestService requestService; - - @Before - public void init() { - cachingService = getService(CachingServiceImpl.class); - requestService = getService(RequestService.class); - assertNotNull(cachingService); - assertNotNull(requestService); - } - - @After - public void tearDown() { - cachingService = null; - requestService = null; - } - - /** - * Test method for {@link org.dspace.services.caching.CachingServiceImpl#reloadConfig()}. - */ - @Test - public void testReloadConfig() { - // just make sure no failure - cachingService.reloadConfig(); - } - - /** - * Test method for {@link org.dspace.services.caching.CachingServiceImpl#notifyForConfigNames()}. - */ - @Test - public void testNotifyForConfigNames() { - assertNotNull(cachingService.notifyForConfigNames()); - } - - /** - * Test method for - * {@link org.dspace.services.caching.CachingServiceImpl#instantiateEhCache(java.lang.String, org.dspace.services.model.CacheConfig)}. - */ - @Test - public void testInstantiateEhCache() { - EhcacheCache cache = cachingService.instantiateEhCache("aaronz-eh", null); // make default ehcache - assertNotNull(cache); - assertEquals("aaronz-eh", cache.getName()); - assertNotNull(cache.getCache()); - assertEquals(0, cache.size()); - assertNotNull(cache.getConfig()); - assertEquals(cache.getConfig().getCacheScope(), CacheScope.INSTANCE); - - EhcacheCache cache2 = cachingService.instantiateEhCache("aaronz-eh", null); - assertNotNull(cache2); - assertEquals(cache2, cache); - - //trash the references - cache = cache2 = null; - } - - /** - * Test method for - * {@link org.dspace.services.caching.CachingServiceImpl#instantiateMapCache(java.lang.String, org.dspace.services.model.CacheConfig)}. - */ - @Test - public void testInstantiateMapCache() { - requestService.startRequest(); - - MapCache cache = cachingService.instantiateMapCache("aaronz-map", null); - assertNotNull(cache); - assertEquals("aaronz-map", cache.getName()); - assertNotNull(cache.getCache()); - assertEquals(0, cache.size()); - assertNotNull(cache.getConfig()); - assertEquals(cache.getConfig().getCacheScope(), CacheScope.REQUEST); - - MapCache cache2 = cachingService.instantiateMapCache("aaronz-map", null); - assertNotNull(cache2); - assertEquals(cache2, cache); - - requestService.endRequest(null); - - //trash the references - cache = cache2 = null; - } - - /** - * Test method for - * {@link org.dspace.services.caching.CachingServiceImpl#getCache(java.lang.String, org.dspace.services.model.CacheConfig)}. - */ - @Test - public void testGetCache() { - // test getting ehcache from the config - Cache cache = cachingService.getCache("org.dspace.caching.MemOnly", null); - assertNotNull(cache); - assertEquals("org.dspace.caching.MemOnly", cache.getName()); - - // test getting ehcache from bean - Cache sampleCache = cachingService.getCache("org.sakaiproject.caching.test.SampleCache", null); - assertNotNull(sampleCache); - assertEquals("org.sakaiproject.caching.test.SampleCache", sampleCache.getName()); - - // test making new caches - Cache c1 = cachingService.getCache("org.dspace.aztest", null); - assertNotNull(c1); - assertEquals("org.dspace.aztest", c1.getName()); - assertEquals(CacheScope.INSTANCE, c1.getConfig().getCacheScope()); - assertTrue(c1 instanceof EhcacheCache); - - requestService.startRequest(); - - Cache rc1 = cachingService.getCache("org.dspace.request.cache1", new CacheConfig(CacheScope.REQUEST)); - assertNotNull(rc1); - assertEquals("org.dspace.request.cache1", rc1.getName()); - assertEquals(CacheScope.REQUEST, rc1.getConfig().getCacheScope()); - assertTrue(rc1 instanceof MapCache); - - requestService.endRequest(null); - - // try getting the same one twice - Cache c2 = cachingService.getCache("org.dspace.aztest", null); - assertNotNull(c2); - assertEquals(c1, c2); - - //trash the references - cache = sampleCache = c1 = rc1 = c2 = null; - - } - - /** - * Test method for {@link org.dspace.services.caching.CachingServiceImpl#getCaches()}. - */ - @Test - public void testGetCaches() { - List caches = cachingService.getCaches(); - assertNotNull(caches); - int curSize = caches.size(); - assertTrue(curSize > 0); - - Cache memCache = cachingService.getCache("org.dspace.caching.MemOnly", null); - assertNotNull(memCache); - assertTrue(caches.contains(memCache)); - - // This should create a new cache (as cache name is unique) - Cache c1 = cachingService.getCache("org.dspace.timtest.newcache", null); - assertNotNull(c1); - - // Test that new cache was created and total caches increases by one - caches = cachingService.getCaches(); - assertNotNull(caches); - assertEquals(curSize + 1, caches.size()); - assertTrue(caches.contains(c1)); - - //trash the references - memCache = c1 = null; - } - - /** - * Test method for {@link org.dspace.services.caching.CachingServiceImpl#getStatus(java.lang.String)}. - */ - @Test - public void testGetStatus() { - String status = cachingService.getStatus(null); - assertNotNull(status); - - // make sure invalid cache is not a failure - status = cachingService.getStatus("XXXXXXXXX"); - assertNotNull(status); - } - - /** - * Test method for {@link org.dspace.services.caching.CachingServiceImpl#resetCaches()}. - */ - @Test - public void testResetCaches() { - cachingService.resetCaches(); - - // now add new cache - Cache c1 = cachingService.getCache("org.dspace.aztest.new", null); - assertNotNull(c1); - c1.put("AZ", "aaron.zeckoski"); - c1.put("BZ", "becky.zeckoski"); - assertEquals("aaron.zeckoski", c1.get("AZ")); - assertEquals(null, c1.get("CZ")); - assertEquals(2, c1.size()); - - cachingService.resetCaches(); - - assertEquals(null, c1.get("AZ")); - assertEquals(0, c1.size()); - - c1 = null; - } - - /** - * Test method for {@link org.dspace.services.caching.CachingServiceImpl#destroyCache(java.lang.String)}. - */ - @Test - public void testDestroyCache() { - // destroy existing cache - Cache cache = cachingService.getCache("org.dspace.caching.MemOnly", null); - assertNotNull(cache); - - cachingService.destroyCache(cache.getName()); - - Cache c2 = cachingService.getCache("org.dspace.caching.MemOnly", null); - assertNotNull(c2); - assertNotSame(cache, c2); - - // ok to destroy non-existent caches - cachingService.destroyCache("XXXXXXXXXXXX"); - - // destroy new cache - Cache ca = cachingService.getCache("org.dspace.aztest", null); - assertNotNull(ca); - - cachingService.destroyCache(ca.getName()); - - Cache cb = cachingService.getCache("org.dspace.aztest", null); - assertNotNull(cb); - assertNotSame(ca, cb); - - //trash the references - cache = c2 = ca = cb = null; - } - -} diff --git a/dspace-services/src/test/java/org/dspace/services/caching/EhcacheCacheTest.java b/dspace-services/src/test/java/org/dspace/services/caching/EhcacheCacheTest.java deleted file mode 100644 index a7328f5f27..0000000000 --- a/dspace-services/src/test/java/org/dspace/services/caching/EhcacheCacheTest.java +++ /dev/null @@ -1,226 +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.services.caching; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import org.dspace.services.caching.model.EhcacheCache; -import org.dspace.services.model.Cache; -import org.dspace.services.model.CacheConfig; -import org.dspace.services.model.CacheConfig.CacheScope; -import org.dspace.test.DSpaceAbstractKernelTest; -import org.ehcache.CacheManager; -import org.ehcache.core.Ehcache; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - - -/** - * Testing the functionality of the DSpace caches - * - * @author Aaron Zeckoski (azeckoski @ gmail.com) - */ -public class EhcacheCacheTest extends DSpaceAbstractKernelTest { - - static String cacheName = "org.dspace.aaronz.test.Cache"; - static CacheManager cacheManager; - static Cache cache = null; - - @BeforeClass - public static void initClass() { - CachingServiceImpl cachingService = getService(CachingServiceImpl.class); - cacheManager = cachingService.getCacheManager(); - cache = cachingService.getCache(cacheName, new CacheConfig(CacheScope.INSTANCE)); - } - - @AfterClass - public static void tearDownClass() { - if (cacheManager != null) { - cacheManager.shutdown(); - } - cacheManager = null; - cache = null; - } - - @Before - public void init() { - cache.clear(); - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#getCache()}. - */ - @Test - public void testGetCache() { - assertTrue(cache instanceof EhcacheCache); - assertNotNull(((EhcacheCache) cache).getCache()); - } - - /** - * Test method for - * {@link org.dspace.services.caching.model.EhcacheCache#EhcacheCache(net.sf.ehcache.Ehcache, org.dspace.services.model.CacheConfig)}. - */ - @Test - public void testEhcacheCache() { - cacheManager.addCache("org.dspace.ehcache"); - Ehcache ehcache = cacheManager.getEhcache("org.dspace.ehcache"); - assertNotNull(ehcache); - - EhcacheCache cache = new EhcacheCache(ehcache, new CacheConfig(CacheScope.INSTANCE)); - assertEquals("org.dspace.ehcache", cache.getName()); - - //trash the references - ehcache = null; - cache = null; - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#getConfig()}. - */ - @Test - public void testGetConfig() { - CacheConfig cacheConfig = cache.getConfig(); - assertNotNull(cacheConfig); - - cacheConfig = null; - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#getName()}. - */ - @Test - public void testGetName() { - String name = cache.getName(); - assertNotNull(name); - assertEquals(cacheName, name); - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#clear()}. - */ - @Test - public void testClear() { - cache.put("XX", "XXXX"); - assertTrue(cache.size() > 0); - cache.clear(); - assertEquals(0, cache.size()); - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#exists(java.lang.String)}. - */ - @Test - public void testExists() { - assertFalse(cache.exists("XXX")); - cache.put("XXX", 123); - assertTrue(cache.exists("XXX")); - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#get(java.lang.String)}. - */ - @Test - public void testGet() { - cache.put("XXX", 123); - Integer i = (Integer) cache.get("XXX"); - assertNotNull(i); - assertEquals(new Integer(123), i); - - Object o = cache.get("YYYYYYYY"); - assertNull(o); - - try { - cache.get(null); - fail("Should have thrown exception"); - } catch (IllegalArgumentException e) { - assertNotNull(e.getMessage()); - } - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#look(java.lang.String)}. - */ - @Test - public void testLook() { - cache.put("XXX", "AZ"); - String thing = (String) cache.look("XXX"); - assertNotNull(thing); - assertEquals("AZ", thing); - - // TODO better way to test this - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#put(java.lang.String, java.lang.Object)}. - */ - @Test - public void testPut() { - assertEquals(0, cache.size()); - cache.put("XXX", 123); - assertEquals(1, cache.size()); - cache.put("YYY", null); - assertEquals(2, cache.size()); - cache.put("XXX", "ABC"); - assertEquals(2, cache.size()); - cache.put("XXX", null); - assertEquals(2, cache.size()); - Object o = cache.get("XXX"); - assertNull(o); - - try { - cache.put(null, "XXX"); - fail("Should have thrown exception"); - } catch (IllegalArgumentException e) { - assertNotNull(e.getMessage()); - } - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#remove(java.lang.String)}. - */ - @Test - public void testRemove() { - cache.put("XXX", 123); - cache.put("YYY", null); - assertEquals(2, cache.size()); - cache.remove("XXX"); - assertEquals(1, cache.size()); - cache.remove("XXX"); - assertEquals(1, cache.size()); - cache.remove("ZZZ"); - assertEquals(1, cache.size()); - - try { - cache.remove(null); - fail("Should have thrown exception"); - } catch (IllegalArgumentException e) { - assertNotNull(e.getMessage()); - } - } - - /** - * Test method for {@link org.dspace.services.caching.model.EhcacheCache#size()}. - */ - @Test - public void testSize() { - assertEquals(0, cache.size()); - cache.put("A", "AASSDDFF"); - assertEquals(1, cache.size()); - cache.put("B", "AASSDDFF"); - cache.put("C", "AASSDDFF"); - assertEquals(3, cache.size()); - } - -} diff --git a/dspace-services/src/test/java/org/dspace/services/events/EventServiceTest.java b/dspace-services/src/test/java/org/dspace/services/events/EventServiceTest.java index afe11c7acd..cbddbf5457 100644 --- a/dspace-services/src/test/java/org/dspace/services/events/EventServiceTest.java +++ b/dspace-services/src/test/java/org/dspace/services/events/EventServiceTest.java @@ -101,53 +101,6 @@ public class EventServiceTest extends DSpaceAbstractKernelTest { assertEquals(event5, listenerBothFilters.getReceivedEvents().get(2)); } - /** - * Test method for - * {@link org.dspace.services.events.SystemEventService#queueEvent(org.dspace.services.model.Event)}. - */ - @Test - public void testQueueEvent() { - Event event1 = new Event("test.event.read", "test-resource-1", "11111", false); - Event event2 = new Event("test.event.jump", null, "11111", false); - Event event3 = new Event("some.event.write", "test-resource-2", "11111", true); - Event event4 = new Event("some.event.add", "fake-resource-555", "11111", true); - - assertEquals(0, listenerNoFilter.getReceivedEvents().size()); - - // no request, so it fires now - eventService.queueEvent(event1); - - // now check that the listeners were properly triggered - assertEquals(1, listenerNoFilter.getReceivedEvents().size()); - - // now start a request - requestService.startRequest(); - eventService.queueEvent(event2); - eventService.queueEvent(event3); - - // not yet fired - assertEquals(1, listenerNoFilter.getReceivedEvents().size()); - - // fail request - requestService.endRequest(new RuntimeException("die request!")); - - // still nothing because fail - assertEquals(1, listenerNoFilter.getReceivedEvents().size()); - - // try a successful one - requestService.startRequest(); - eventService.queueEvent(event2); - eventService.queueEvent(event3); - eventService.queueEvent(event4); - requestService.endRequest(null); - - assertEquals(4, listenerNoFilter.getReceivedEvents().size()); - assertEquals(event1, listenerNoFilter.getReceivedEvents().get(0)); - assertEquals(event2, listenerNoFilter.getReceivedEvents().get(1)); - assertEquals(event3, listenerNoFilter.getReceivedEvents().get(2)); - assertEquals(event4, listenerNoFilter.getReceivedEvents().get(3)); - } - /** * Test method for * {@link org.dspace.services.events.SystemEventService#registerEventListener(org.dspace.services.model.EventListener)}. diff --git a/dspace-services/src/test/java/org/dspace/services/session/StatelessRequestServiceImplTest.java b/dspace-services/src/test/java/org/dspace/services/session/StatelessRequestServiceImplTest.java index 4fccf9f36a..7b41a295e3 100644 --- a/dspace-services/src/test/java/org/dspace/services/session/StatelessRequestServiceImplTest.java +++ b/dspace-services/src/test/java/org/dspace/services/session/StatelessRequestServiceImplTest.java @@ -13,17 +13,12 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import org.dspace.services.CachingService; -import org.dspace.services.model.Cache; -import org.dspace.services.model.CacheConfig; -import org.dspace.services.model.CacheConfig.CacheScope; import org.dspace.services.sessions.StatelessRequestServiceImpl; import org.dspace.test.DSpaceAbstractKernelTest; import org.junit.After; import org.junit.Before; import org.junit.Test; - /** * Testing the request and session services * @@ -32,20 +27,16 @@ import org.junit.Test; public class StatelessRequestServiceImplTest extends DSpaceAbstractKernelTest { private StatelessRequestServiceImpl statelessRequestService; - private CachingService cachingService; @Before public void before() { statelessRequestService = getService(StatelessRequestServiceImpl.class); - cachingService = getService(CachingService.class); } @After public void after() { statelessRequestService.clear(); - cachingService.resetCaches(); statelessRequestService = null; - cachingService = null; } /** @@ -68,7 +59,6 @@ public class StatelessRequestServiceImplTest extends DSpaceAbstractKernelTest { assertNotNull(requestId); statelessRequestService.endRequest(null); - assertNull(getRequestCache()); } /** @@ -140,13 +130,4 @@ public class StatelessRequestServiceImplTest extends DSpaceAbstractKernelTest { requestId = statelessRequestService.getCurrentRequestId(); assertNull(requestId); // no request yet } - - - /** - * @return the request storage cache - */ - private Cache getRequestCache() { - return cachingService.getCache(CachingService.REQUEST_CACHE, new CacheConfig(CacheScope.REQUEST)); - } - } diff --git a/dspace-services/src/test/resources/ehcache-config.xml b/dspace-services/src/test/resources/ehcache-config.xml deleted file mode 100644 index 363a61ee70..0000000000 --- a/dspace-services/src/test/resources/ehcache-config.xml +++ /dev/null @@ -1,527 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-services/src/test/resources/spring/spring-test-beans.xml b/dspace-services/src/test/resources/spring/spring-test-beans.xml index ed53d7f8f4..60564bee99 100644 --- a/dspace-services/src/test/resources/spring/spring-test-beans.xml +++ b/dspace-services/src/test/resources/spring/spring-test-beans.xml @@ -22,17 +22,6 @@ @Configuration annotations, e.g. see org.dspace.servicemanager.config.TestDynamicAnnotationConfiguration --> - - - - - - - - - - - diff --git a/pom.xml b/pom.xml index 7d0d9b058a..87289f12ca 100644 --- a/pom.xml +++ b/pom.xml @@ -28,13 +28,14 @@ 8.8.1 1.2.22 - 3.1.3 + 3.4.0 2.3.4 2.12.3 1.3.2 2.3.1 2.3.1 + 1.1.0 9.4.41.v20210516 2.17.1 @@ -1136,6 +1137,12 @@ ${hibernate.version} + + javax.cache + cache-api + ${jcache-version} + + org.hibernate hibernate-validator From 09591dc6ff107999da080d6c5dce89e51fae5485 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 2 Feb 2022 12:09:24 -0500 Subject: [PATCH 0693/1254] Rewrite Hibernate Ehcache configuration for v3. #8099 Also fix incorrectly-used Spring-style elements in hibernate.cfg.xml --- dspace/config/hibernate-ehcache-config.xml | 203 +++++++++++++-------- dspace/config/hibernate.cfg.xml | 10 +- 2 files changed, 128 insertions(+), 85 deletions(-) diff --git a/dspace/config/hibernate-ehcache-config.xml b/dspace/config/hibernate-ehcache-config.xml index 5e1c18313b..bd2f28f482 100644 --- a/dspace/config/hibernate-ehcache-config.xml +++ b/dspace/config/hibernate-ehcache-config.xml @@ -8,103 +8,144 @@ http://www.dspace.org/license/ --> - + - + + + + + - - + + + 1200 + + + 3000 + + + - - + + + + + + 6000 + - + + + 600 + + 2000 + - - + + + + 86400 + + 1 + - - + + + + 3600 + + 100 + - - + + + + 3600 + + 2000 + - + - - + + + + 3600 + + 5000 + - + - - + + + + 1800 + + 1000 + - - + + + + 1800 + + + 4000 + + + - - + + + + 1800 + + + 2000 + + + - + diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index de8d7add00..e13bcc5644 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -22,10 +22,12 @@ true true true - - + + org.hibernate.cache.jcache.JCacheRegionFactory + + + org.ehcache.jsr107.EhcacheCachingProvider + From f36ce778fb55d3363aaee72cb7719b5ae5738d2a Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 3 Feb 2022 08:48:33 -0500 Subject: [PATCH 0694/1254] Invent 'solr.multicorePrefix' to distinguish multiple DSpace instances running against a single Solr instance. #8129 --- dspace/config/dspace.cfg | 16 +++++++++++----- dspace/config/modules/discovery.cfg | 2 +- dspace/config/modules/iiif.cfg | 2 +- dspace/config/modules/oai.cfg | 2 +- dspace/config/modules/solr-statistics.cfg | 2 +- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 03865bafc3..3345287d59 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -42,9 +42,14 @@ default.language = en_US # Solr server/webapp. # DSpace uses Solr for all search/browse capability (and for usage statistics). -# Since DSpace 7, SOLR must be installed as a stand-alone service +# Since DSpace 7, SOLR must be installed as a stand-alone service. solr.server = http://localhost:8983/solr +# Solr core name prefix. +# If you connect multiple instances of DSpace to a single Solr instance, you +# can organize them with a common core name prefix. +solr.multicorePrefix = + # Solr connection pool. # If you change these values, the changes are not effective until DSpace is # restarted. @@ -1349,11 +1354,11 @@ sherpa.romeo.apikey = # org.dspace.content.authority.SampleAuthority = Sample, \ # org.dspace.content.authority.SHERPARoMEOPublisher = SRPublisher, \ # org.dspace.content.authority.SHERPARoMEOJournalTitle = SRJournalTitle, \ -# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority +# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority #Uncomment to enable ORCID authority control #plugin.named.org.dspace.content.authority.ChoiceAuthority = \ -# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority +# org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority # URL of ORCID API # Defaults to using the Public API V3 (pub.orcid.org) @@ -1403,8 +1408,9 @@ plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ ## See manual or org.dspace.content.authority.Choices source for descriptions. authority.minconfidence = ambiguous -# Configuration settings for ORCID based authority control, uncomment the lines below to enable configuration -#solr.authority.server=${solr.server}/authority +# Configuration settings for ORCID based authority control. +# Uncomment the lines below to enable configuration. +#solr.authority.server=${solr.server}/${solr.multicorePrefix}authority #choices.plugin.dc.contributor.author = SolrAuthorAuthority #choices.presentation.dc.contributor.author = authorLookup #authority.controlled.dc.contributor.author = true diff --git a/dspace/config/modules/discovery.cfg b/dspace/config/modules/discovery.cfg index 121aea11ac..72088ddc49 100644 --- a/dspace/config/modules/discovery.cfg +++ b/dspace/config/modules/discovery.cfg @@ -5,7 +5,7 @@ # faceted-search system. # #---------------------------------------------------------------# ##### Search Indexing ##### -discovery.search.server = ${solr.server}/search +discovery.search.server = ${solr.server}/${solr.multicorePrefix}search #Enable the url validation of the search.server setting above. #Defaults to true: validation is enabled diff --git a/dspace/config/modules/iiif.cfg b/dspace/config/modules/iiif.cfg index d6820b5305..22a47aac50 100644 --- a/dspace/config/modules/iiif.cfg +++ b/dspace/config/modules/iiif.cfg @@ -10,7 +10,7 @@ iiif.image.server = http://localhost:8182/iiif/2/ # Base URL of the search service used to support the (experimental) IIIF Search # capability. The actual path will depend on how search is being supported. # This example uses the solr plugin https://dbmdz.github.io/solr-ocrhighlighting/ -# iiif.search.url = ${solr.server}/word_highlighting +# iiif.search.url = ${solr.server}/${solr.multicorePrefix}word_highlighting # The search plugin used to support (experimental) IIIF Search. # This is the class used with https://dbmdz.github.io/solr-ocrhighlighting/ diff --git a/dspace/config/modules/oai.cfg b/dspace/config/modules/oai.cfg index 79488afc29..98b10f59de 100644 --- a/dspace/config/modules/oai.cfg +++ b/dspace/config/modules/oai.cfg @@ -24,7 +24,7 @@ oai.storage=solr oai.url = ${dspace.server.url}/${oai.path} # Base solr index -oai.solr.url=${solr.server}/oai +oai.solr.url=${solr.server}/${solr.multicorePrefix}oai # OAI persistent identifier prefix # This field is used for two purposes: diff --git a/dspace/config/modules/solr-statistics.cfg b/dspace/config/modules/solr-statistics.cfg index 5b67cd4799..073850ca23 100644 --- a/dspace/config/modules/solr-statistics.cfg +++ b/dspace/config/modules/solr-statistics.cfg @@ -10,7 +10,7 @@ # set this to be the port you run the dspace "solr" webapp # on, by default, we are assuming a test configuration with # tomcat still running on port 8080 -solr-statistics.server = ${solr.server}/statistics +solr-statistics.server = ${solr.server}/${solr.multicorePrefix}statistics # A comma-separated list that contains the bundles for which the bitstreams will be displayed solr-statistics.query.filter.bundles=ORIGINAL From ef0adf5f9b968b29065171f8bb16861d32d08238 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 3 Feb 2022 11:25:57 -0500 Subject: [PATCH 0695/1254] First cut at better exception logging. #8148 --- .../DSpaceApiExceptionControllerAdvice.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 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 d57e637047..cd740e17a0 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 @@ -54,7 +54,7 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH private static final Logger log = LogManager.getLogger(DSpaceApiExceptionControllerAdvice.class); /** - * Set of HTTP error codes to log as ERROR with full stacktrace. + * Set of HTTP error codes to log as ERROR with full stack trace. */ private static final Set LOG_AS_ERROR = Set.of(422); @@ -126,8 +126,14 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH /** * Add user-friendly error messages to the response body for selected errors. - * Since the error messages will be exposed to the API user, the exception classes are expected to implement - * {@link TranslatableException} such that the error messages can be translated. + * Since the error messages will be exposed to the API user, the + * exception classes are expected to implement {@link TranslatableException} + * such that the error messages can be translated. + * + * @param request the client's request + * @param response our response + * @param ex exception thrown in handling request + * @throws java.io.IOException passed through. */ @ExceptionHandler({ RESTEmptyWorkflowGroupException.class, @@ -192,8 +198,9 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH /** * Send the error to the response. - * 5xx errors will be logged as ERROR with a full stack trace, 4xx errors will be logged as WARN without a - * stacktrace. Specific 4xx errors where an ERROR log with full stacktrace is more appropriate are configured in + * 5xx errors will be logged as ERROR with a full stack trace. 4xx errors + * will be logged as WARN without a stack trace. Specific 4xx errors where + * an ERROR log with full stack trace is more appropriate are configured in * {@link #LOG_AS_ERROR} * @param request current request * @param response current response @@ -202,8 +209,10 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH * @param statusCode status code to send in response * @throws IOException */ - private void sendErrorResponse(final HttpServletRequest request, final HttpServletResponse response, - final Exception ex, final String message, final int statusCode) throws IOException { + private void sendErrorResponse(final HttpServletRequest request, + final HttpServletResponse response, + final Exception ex, final String message, final int statusCode) + throws IOException { //Make sure Spring picks up this exception request.setAttribute(EXCEPTION_ATTRIBUTE, ex); @@ -213,7 +222,10 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH log.error("{} (status:{})", message, statusCode, ex); } else if (HttpStatus.valueOf(statusCode).is4xxClientError()) { // Log the error as a single-line WARN - log.warn("{} (status:{})", message, statusCode); + StackTraceElement[] trace = ex.getStackTrace(); + String location = trace.length <= 0 ? "unknown" : trace[0].toString(); + log.warn("{} (status:{} exception: {} at: {})", message, statusCode, + ex.getMessage(), location); } //Exception properties will be set by org.springframework.boot.web.support.ErrorPageFilter From 8470899fd6efab056508726dbffceed15bf4b3eb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 3 Feb 2022 12:04:00 -0600 Subject: [PATCH 0696/1254] Update LICENSES_THIRD_PARTY for 7.2 release --- LICENSES_THIRD_PARTY | 99 +++++++++++++++++++++++++++++++++----------- pom.xml | 8 ++-- 2 files changed, 79 insertions(+), 28 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index c0cfe62a78..f918af1c3e 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -21,9 +21,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines Apache Software License, Version 2.0: * Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net) - * AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.10.50 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.10.50 - https://aws.amazon.com/sdkforjava) - * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.10.50 - https://aws.amazon.com/sdkforjava) + * AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.116 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.116 - https://aws.amazon.com/sdkforjava) + * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.116 - https://aws.amazon.com/sdkforjava) + * JMES Path Query library (com.amazonaws:jmespath-java:1.12.116 - https://aws.amazon.com/sdkforjava) * jcommander (com.beust:jcommander:1.78 - https://jcommander.org) * HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc) * parso (com.epam:parso:2.0.11 - https://github.com/epam/parso) @@ -31,13 +32,24 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.12.3 - http://github.com/FasterXML/jackson) * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.12.3 - https://github.com/FasterXML/jackson-core) * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.12.3 - http://github.com/FasterXML/jackson) + * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.3 - http://github.com/FasterXML/jackson-dataformats-binary) * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.11.2 - 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) * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.10.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) + * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.10.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) + * Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.0.1 - https://github.com/cowtowncoder/java-uuid-generator) * Woodstox (com.fasterxml.woodstox:woodstox-core:5.0.3 - https://github.com/FasterXML/woodstox) * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.6 - https://github.com/flipkart-incubator/zjsonpatch/) * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.8.4 - https://github.com/ben-manes/caffeine) + * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) + * jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils) + * jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils) + * json-schema-core (com.github.java-json-tools:json-schema-core:1.2.14 - https://github.com/java-json-tools/json-schema-core) + * json-schema-validator (com.github.java-json-tools:json-schema-validator:2.2.14 - https://github.com/java-json-tools/json-schema-validator) + * msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple) + * uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template) * Open JSON (com.github.openjson:openjson:1.0.12 - https://github.com/openjson/openjson) * JCIP Annotations under Apache License (com.github.stephenc.jcip:jcip-annotations:1.0-1 - http://stephenc.github.com/jcip-annotations) * Google APIs Client Library for Java (com.google.api-client:google-api-client:1.23.0 - https://github.com/google/google-api-java-client/google-api-client) @@ -52,9 +64,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.23.0 - https://github.com/google/google-http-java-client/google-http-client) * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.23.0 - https://github.com/google/google-http-java-client/google-http-client-jackson2) * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) - * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.23.0 - https://github.com/google/google-oauth-java-client/google-oauth-client) + * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.32.1 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client) * ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap) * JSON.simple (com.googlecode.json-simple:json-simple:1.1.1 - http://code.google.com/p/json-simple/) + * libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/) * Jackcess (com.healthmarketscience.jackcess:jackcess:3.0.1 - https://jackcess.sourceforge.io) * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:3.0.0 - http://jackcessencrypt.sf.net) * project ':json-path' (com.jayway.jsonpath:json-path:2.4.0 - https://github.com/jayway/JsonPath) @@ -94,28 +107,48 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm) * Netty (io.netty:netty:3.10.6.Final - http://netty.io/) * Netty/Buffer (io.netty:netty-buffer:4.1.50.Final - https://netty.io/netty-buffer/) + * Netty/Buffer (io.netty:netty-buffer:4.1.68.Final - https://netty.io/netty-buffer/) * Netty/Codec (io.netty:netty-codec:4.1.50.Final - https://netty.io/netty-codec/) + * Netty/Codec (io.netty:netty-codec:4.1.68.Final - https://netty.io/netty-codec/) + * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.53.Final - https://netty.io/netty-codec-http/) + * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.53.Final - https://netty.io/netty-codec-socks/) * Netty/Common (io.netty:netty-common:4.1.50.Final - https://netty.io/netty-common/) + * Netty/Common (io.netty:netty-common:4.1.68.Final - https://netty.io/netty-common/) * Netty/Handler (io.netty:netty-handler:4.1.50.Final - https://netty.io/netty-handler/) + * Netty/Handler (io.netty:netty-handler:4.1.68.Final - https://netty.io/netty-handler/) + * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.53.Final - https://netty.io/netty-handler-proxy/) * Netty/Resolver (io.netty:netty-resolver:4.1.50.Final - https://netty.io/netty-resolver/) * Netty/Transport (io.netty:netty-transport:4.1.50.Final - https://netty.io/netty-transport/) + * Netty/Transport (io.netty:netty-transport:4.1.68.Final - https://netty.io/netty-transport/) * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.50.Final - https://netty.io/netty-transport-native-epoll/) * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.50.Final - https://netty.io/netty-transport-native-unix-common/) * OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api) * OpenTracing-noop (io.opentracing:opentracing-noop:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-noop) * OpenTracing-util (io.opentracing:opentracing-util:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-util) * Google S2 geometry library (io.sgr:s2-geometry-library-java:1.0.0 - https://github.com/sgr-io/s2-geometry-library-java) + * swagger-annotations (io.swagger:swagger-annotations:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) + * swagger-compat-spec-parser (io.swagger:swagger-compat-spec-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-compat-spec-parser) + * swagger-core (io.swagger:swagger-core:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-core) + * swagger-models (io.swagger:swagger-models:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-models) + * swagger-parser (io.swagger:swagger-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) + * swagger-annotations (io.swagger.core.v3:swagger-annotations:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) + * swagger-core (io.swagger.core.v3:swagger-core:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-core) + * swagger-models (io.swagger.core.v3:swagger-models:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-models) + * swagger-parser (io.swagger.parser.v3:swagger-parser:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) + * swagger-parser (io.swagger.parser.v3:swagger-parser-core:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-core) + * swagger-parser-v2-converter (io.swagger.parser.v3:swagger-parser-v2-converter:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter) + * swagger-parser-v3 (io.swagger.parser.v3:swagger-parser-v3:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3) * Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:2.0.2 - https://beanvalidation.org) * JSR107 API and SPI (javax.cache:cache-api:1.1.0 - https://github.com/jsr107/jsr107spec) * javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/) * Bean Validation API (javax.validation:validation-api:2.0.1.Final - http://beanvalidation.org) * jdbm (jdbm:jdbm:1.0 - no url defined) * Joda-Time (joda-time:joda-time:2.9.2 - http://www.joda.org/joda-time/) - * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.10.2 - https://bytebuddy.net/byte-buddy) - * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.10.20 - https://bytebuddy.net/byte-buddy) - * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.10.20 - https://bytebuddy.net/byte-buddy-agent) + * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy) + * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent) * eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties) * Java Native Access (net.java.dev.jna:jna:5.5.0 - https://github.com/java-native-access/jna) + * json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.19.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core) * "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/) * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:1.2 - http://www.minidev.net/) * JSON Small and Fast Parser (net.minidev:json-smart:2.3 - http://www.minidev.net/) @@ -139,6 +172,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * 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.8 - https://commons.apache.org/proper/commons-text) + * Apache Commons Text (org.apache.commons:commons-text:1.9 - 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) @@ -169,12 +203,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util) * Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1) * Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix) - * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.13.3 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/) - * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.13.3 - https://logging.apache.org/log4j/2.x/log4j-api/) - * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.13.3 - https://logging.apache.org/log4j/2.x/log4j-core/) - * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.13.3 - https://logging.apache.org/log4j/2.x/log4j-jul/) - * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.13.3 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) - * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.13.3 - https://logging.apache.org/log4j/2.x/log4j-web/) + * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/) + * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-api/) + * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-core/) + * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-jul/) + * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/) + * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-web/) * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.8.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.8.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.8.1 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) @@ -229,7 +263,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.33 - https://tomcat.apache.org/) * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.33 - https://tomcat.apache.org/) * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.33 - https://tomcat.apache.org/) - * Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.0 - http://velocity.apache.org/engine/devel/velocity-engine-core/) + * Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.2 - http://velocity.apache.org/engine/devel/velocity-engine-core/) + * Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.2 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/) * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.22 - http://ws.apache.org/axiom/) * LLOM (org.apache.ws.commons.axiom:axiom-impl:1.2.22 - http://ws.apache.org/axiom/implementations/axiom-impl/) * Abdera Model (FOM) Implementation (org.apache.ws.commons.axiom:fom-impl:1.2.22 - http://ws.apache.org/axiom/implementations/fom-impl/) @@ -293,13 +328,17 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jtwig-spring-boot-starter (org.jtwig:jtwig-spring-boot-starter:5.87.0.RELEASE - http://jtwig.org) * jtwig-web (org.jtwig:jtwig-web:5.87.0.RELEASE - http://jtwig.org) * Spatial4J (org.locationtech.spatial4j:spatial4j:0.7 - https://projects.eclipse.org/projects/locationtech.spatial4j) + * MockServer Java Client (org.mock-server:mockserver-client-java:5.11.2 - http://www.mock-server.com) + * MockServer Core (org.mock-server:mockserver-core:5.11.2 - http://www.mock-server.com) + * MockServer JUnit 4 Integration (org.mock-server:mockserver-junit-rule:5.11.2 - http://www.mock-server.com) + * MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.11.2 - http://www.mock-server.com) * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:apache-el:8.5.35.1 - https://github.com/jetty-project/jasper-jsp/apache-el) * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:8.5.35.1 - https://github.com/jetty-project/jasper-jsp/apache-jsp) * Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) * Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester) * Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util) * Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api) - * Objenesis (org.objenesis:objenesis:3.1 - http://objenesis.org) + * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) * parboiled-core (org.parboiled:parboiled-core:1.3.1 - http://parboiled.org) * parboiled-java (org.parboiled:parboiled-java:1.3.1 - http://parboiled.org) * quartz (org.quartz-scheduler:quartz:2.3.2 - http://www.quartz-scheduler.org/quartz) @@ -351,10 +390,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * XMPCore Shaded (org.tallison.xmp:xmpcore-shaded:6.1.10 - https://github.com/tballison) * snappy-java (org.xerial.snappy:snappy-java:1.1.7.6 - https://github.com/xerial/snappy-java) * xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/) - * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.6.3 - https://www.xmlunit.org/) * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.6.4 - https://www.xmlunit.org/) + * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.8.0 - https://www.xmlunit.org/) + * org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.8.0 - https://www.xmlunit.org/xmlunit-placeholders/) * SnakeYAML (org.yaml:snakeyaml:1.25 - http://www.snakeyaml.org) + * SnakeYAML (org.yaml:snakeyaml:1.26 - http://www.snakeyaml.org) * ROME, RSS and atOM utilitiEs for Java (rome:rome:1.0 - https://rome.dev.java.net/) + * software.amazon.ion:ion-java (software.amazon.ion:ion-java:1.0.2 - https://github.com/amznlabs/ion-java/) * xalan (xalan:xalan:2.7.0 - no url defined) * Xerces2-j (xerces:xercesImpl:2.12.0 - https://xerces.apache.org/xerces2-j/) * XML Commons External Components XML APIs (xml-apis:xml-apis:1.4.01 - http://xml.apache.org/commons/components/external/) @@ -367,6 +409,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JSONLD Java :: Core (com.github.jsonld-java:jsonld-java:0.5.1 - http://github.com/jsonld-java/jsonld-java/jsonld-java/) * curvesapi (com.github.virtuald:curvesapi:1.06 - https://github.com/virtuald/curvesapi) * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.11.0 - https://developers.google.com/protocol-buffers/protobuf-java/) + * JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/) * dnsjava (dnsjava:dnsjava:2.1.7 - http://www.dnsjava.org) * Units of Measurement API (javax.measure:unit-api:1.0 - http://unitsofmeasurement.github.io/) * jaxen (jaxen:jaxen:1.1.6 - http://jaxen.codehaus.org/) @@ -389,7 +432,8 @@ 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.2.24 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.2.25 - 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/) @@ -397,6 +441,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JavaBeans Activation Framework (com.sun.activation:javax.activation:1.2.0 - http://java.net/all/javax.activation/) * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.7 - http://java.net/istack-commons/istack-commons-runtime/) + * JavaMail API (com.sun.mail:javax.mail:1.6.2 - http://javaee.github.io/javamail/javax.mail) + * JavaMail API (no providers) (com.sun.mail:mailapi:1.6.2 - http://javaee.github.io/javamail/mailapi) * Old JAXB Core (com.sun.xml.bind:jaxb-core:2.3.0.1 - http://jaxb.java.net/jaxb-bundles/jaxb-core) * Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:2.3.1 - http://jaxb.java.net/jaxb-bundles/jaxb-impl) * saaj-impl (com.sun.xml.messaging.saaj:saaj-impl:1.4.0-b03 - http://java.net/saaj-impl/) @@ -405,7 +451,6 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp) * JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/) * javax.annotation API (javax.annotation:javax.annotation-api:1.3.2 - http://jcp.org/en/jsr/detail?id=250) - * JavaMail API (compat) (javax.mail:mail:1.4.7 - http://kenai.com/projects/javamail/mail) * Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net) * javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net) * jaxb-api (javax.xml.bind:jaxb-api:2.3.1 - https://github.com/javaee/jaxb-spec/jaxb-api) @@ -500,6 +545,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines GNU Lesser General Public License (LGPL): + * btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf) + * jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils) + * jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils) + * json-schema-core (com.github.java-json-tools:json-schema-core:1.2.14 - https://github.com/java-json-tools/json-schema-core) + * json-schema-validator (com.github.java-json-tools:json-schema-validator:2.2.14 - https://github.com/java-json-tools/json-schema-validator) + * msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple) + * uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template) * SpotBugs Annotations (com.github.spotbugs:spotbugs-annotations:3.1.9 - https://spotbugs.github.io/) * FindBugs-Annotations (com.google.code.findbugs:annotations:3.0.1u2 - http://findbugs.sourceforge.net/) * c3p0 (com.mchange:c3p0:0.9.5.5 - https://github.com/swaldman/c3p0) @@ -537,6 +589,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * HttpClient Wrappers (edu.ucar:httpservices:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/documentation.htm) * netCDF-4 IOSP JNI connection to C library (edu.ucar:netcdf4:4.5.5 - http://www.unidata.ucar.edu/software/netcdf-java/netcdf4/) * udunits (edu.ucar:udunits:4.5.5 - http://www.unidata.ucar.edu/software/udunits//) + * JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple) * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk15on:1.65 - http://www.bouncycastle.org/java.html) * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.65 - http://www.bouncycastle.org/java.html) * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.65 - http://www.bouncycastle.org/java.html) @@ -546,12 +599,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.30.1 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * Itadaki jbzip2 (org.itadaki:bzip2:0.9.1 - https://code.google.com/p/jbzip2/) * jsoup Java HTML Parser (org.jsoup:jsoup:1.13.1 - https://jsoup.org/) - * mockito-core (org.mockito:mockito-core:3.8.0 - https://github.com/mockito/mockito) - * mockito-inline (org.mockito:mockito-inline:3.8.0 - https://github.com/mockito/mockito) + * mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito) + * mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito) * ORCID - Model (org.orcid:orcid-model:3.0.2 - http://github.com/ORCID/orcid-model) * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.25 - http://www.slf4j.org) * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.25 - http://www.slf4j.org) * SLF4J API Module (org.slf4j:slf4j-api:1.7.25 - http://www.slf4j.org) + * SLF4J Extensions Module (org.slf4j:slf4j-ext:1.7.28 - http://www.slf4j.org) * toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org) * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.5.1 - https://www.webjars.org) * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.5.2 - https://www.webjars.org) @@ -562,6 +616,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * h2 (com.h2database:h2:1.4.187 - no url defined) * Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/) * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/) + * Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino) OGC copyright: @@ -579,10 +634,6 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * JSON in Java (org.json:json:20180130 - https://github.com/douglascrockford/JSON-java) - The New BSD License: - - * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) - UnRar License: * Java UnRar (com.github.junrar:junrar:4.0.0 - https://github.com/junrar/junrar) diff --git a/pom.xml b/pom.xml index d3aaee0d76..aef855bb65 100644 --- a/pom.xml +++ b/pom.xml @@ -692,14 +692,14 @@ src/main/license/third-party-file-groupByLicense.ftl - Apache Software License, Version 2.0|The Apache Software License, Version 2.0|Apache License Version 2.0|Apache License, Version 2.0|Apache Public License 2.0|Apache License 2.0|Apache Software License - Version 2.0|Apache 2.0 License|Apache 2.0 license|Apache License V2.0|Apache 2|Apache License|Apache|ASF 2.0|Apache 2.0|Apache License v2|Apache License v2.0|Apache License, 2.0|Apache License, version 2.0|Apache-2.0|The Apache License, Version 2.0 + Apache Software License, Version 2.0|Apache Software License, version 2.0|The Apache Software License, Version 2.0|Apache License Version 2.0|Apache License, Version 2.0|Apache Public License 2.0|Apache License 2.0|Apache Software License - Version 2.0|Apache 2.0 License|Apache 2.0 license|Apache License V2.0|Apache 2|Apache License|Apache|ASF 2.0|Apache 2.0|Apache License v2|Apache License v2.0|Apache License, 2.0|Apache License, version 2.0|Apache-2.0|The Apache License, Version 2.0 Apache Software License, Version 2.0|http://ant-contrib.sourceforge.net/tasks/LICENSE.txt Apache Software License, Version 2.0|The SAX License|The W3C License Apache Software License, Version 2.0|Similar to Apache License but with the acknowledgment clause removed - BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD + BSD License|The BSD License|BSD licence|BSD license|BSD|BSD-style license|New BSD License|New BSD license|Revised BSD License|BSD 2-Clause license|3-Clause BSD License|BSD 2-Clause|BSD 3-clause New License|BSD Licence 3|BSD-2-Clause|BSD-3-Clause|Modified BSD|The New BSD License BSD License|DSpace BSD License|DSpace Sourcecode License @@ -725,7 +725,7 @@ Eclipse Public License|Common Public License Version 1.0 Eclipse Public License|The GNU General Public License (GPL), Version 2, With Classpath Exception - GNU Lesser General Public License (LGPL)|The GNU Lesser General Public License, Version 2.1|GNU Lesser General Public License (LGPL), Version 2.1|GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1|GNU Lesser General Public License, version 2.1|GNU LESSER GENERAL PUBLIC LICENSE|GNU Lesser General Public License|GNU Lesser Public License|GNU Lesser General Public License, Version 2.1|Lesser General Public License (LGPL) v 2.1|LGPL 2.1|LGPL 2.1 license|LGPL 3.0 license|LGPL, v2.1 or later|LGPL|LGPL, version 2.1|lgpl + GNU Lesser General Public License (LGPL)|The GNU Lesser General Public License, Version 2.1|GNU Lesser General Public License (LGPL), Version 2.1|GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1|GNU Lesser General Public License, version 2.1|GNU LESSER GENERAL PUBLIC LICENSE|GNU Lesser General Public License|GNU Lesser Public License|GNU Lesser General Public License, Version 2.1|Lesser General Public License (LGPL) v 2.1|LGPL 2.1|LGPL 2.1 license|LGPL 3.0 license|LGPL, v2.1 or later|LGPL|LGPL, version 2.1|lgpl|Lesser General Public License, version 3 or greater GNU Lesser General Public License (LGPL)|GNU Library General Public License v2.1 or later MIT License|The MIT License|MIT LICENSE|MIT|MIT license|MIT License (MIT) @@ -733,7 +733,7 @@ MIT License|Bouncy Castle Licence MIT License|(MIT-style) netCDF C library license - Mozilla Public License|Mozilla Public License version 1.1|Mozilla Public License 1.1 (MPL 1.1)|MPL 1.1|Mozilla Public License Version 2.0 + Mozilla Public License|Mozilla Public License version 1.1|Mozilla Public License 1.1 (MPL 1.1)|MPL 1.1|Mozilla Public License Version 2.0|Mozilla Public License, Version 2.0 Mozilla Public License|MPL 2.0, and EPL 1.0 From 122924af824247651468c0717d0cb9f2e0d8d995 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 3 Feb 2022 13:14:08 -0600 Subject: [PATCH 0697/1254] [maven-release-plugin] prepare release dspace-7.2 --- 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 02c9c10677..f000f2e1ba 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index a0e123dcd0..ce1dc8a326 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 17315ee3b5..f4acb0d297 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 4207edc760..8a25dece7e 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 103c1b5e1d..c60383384f 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.2-SNAPSHOT + 7.2 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-SNAPSHOT + 7.2 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 24959edd93..20395013ce 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index e09253a75c..7f08f304d7 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 9745b595eb..a55e0457a9 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 4068853198..a9a58cff06 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index c4d0776052..88ddce8b32 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 73d58d6876..7deb4bd0d0 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 39b457fd9a..6f4097f3d7 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.2-SNAPSHOT + 7.2 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 30095ca578..fadd08b014 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-SNAPSHOT + 7.2 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index a4912ab169..54819ee041 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.2-SNAPSHOT + 7.2 ../pom.xml diff --git a/pom.xml b/pom.xml index aef855bb65..1f12c0c9f2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.2-SNAPSHOT + 7.2 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -868,14 +868,14 @@ org.dspace dspace-rest - 7.2-SNAPSHOT + 7.2 jar classes org.dspace dspace-rest - 7.2-SNAPSHOT + 7.2 war @@ -1026,69 +1026,69 @@ org.dspace dspace-api - 7.2-SNAPSHOT + 7.2 org.dspace dspace-api test-jar - 7.2-SNAPSHOT + 7.2 test org.dspace.modules additions - 7.2-SNAPSHOT + 7.2 org.dspace dspace-sword - 7.2-SNAPSHOT + 7.2 org.dspace dspace-swordv2 - 7.2-SNAPSHOT + 7.2 org.dspace dspace-oai - 7.2-SNAPSHOT + 7.2 org.dspace dspace-services - 7.2-SNAPSHOT + 7.2 org.dspace dspace-server-webapp test-jar - 7.2-SNAPSHOT + 7.2 test org.dspace dspace-rdf - 7.2-SNAPSHOT + 7.2 org.dspace dspace-iiif - 7.2-SNAPSHOT + 7.2 org.dspace dspace-server-webapp - 7.2-SNAPSHOT + 7.2 jar classes org.dspace dspace-server-webapp - 7.2-SNAPSHOT + 7.2 war @@ -1877,7 +1877,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.2 From 18c524170af708196bce9673ef3e186bdcd9caff Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 3 Feb 2022 13:14:11 -0600 Subject: [PATCH 0698/1254] [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 f000f2e1ba..f844493683 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index ce1dc8a326..4713906d87 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index f4acb0d297..27adc3ef94 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 8a25dece7e..b6209efb1c 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index c60383384f..4433cffd33 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.2 + 7.3-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.3-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 20395013ce..e34d3ad1bc 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 7f08f304d7..524244f5a4 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index a55e0457a9..8a01cc5d9c 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index a9a58cff06..3fbc6cf469 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 88ddce8b32..9df5c8ef02 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 7deb4bd0d0..a6bd4a4383 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 6f4097f3d7..161ce7626b 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.2 + 7.3-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index fadd08b014..c789cf5054 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.3-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 54819ee041..081f3ed60d 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.2 + 7.3-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 1f12c0c9f2..6dbc35d8ac 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.2 + 7.3-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.3-SNAPSHOT jar classes org.dspace dspace-rest - 7.2 + 7.3-SNAPSHOT war @@ -1026,69 +1026,69 @@ org.dspace dspace-api - 7.2 + 7.3-SNAPSHOT org.dspace dspace-api test-jar - 7.2 + 7.3-SNAPSHOT test org.dspace.modules additions - 7.2 + 7.3-SNAPSHOT org.dspace dspace-sword - 7.2 + 7.3-SNAPSHOT org.dspace dspace-swordv2 - 7.2 + 7.3-SNAPSHOT org.dspace dspace-oai - 7.2 + 7.3-SNAPSHOT org.dspace dspace-services - 7.2 + 7.3-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.2 + 7.3-SNAPSHOT test org.dspace dspace-rdf - 7.2 + 7.3-SNAPSHOT org.dspace dspace-iiif - 7.2 + 7.3-SNAPSHOT org.dspace dspace-server-webapp - 7.2 + 7.3-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.2 + 7.3-SNAPSHOT war @@ -1877,7 +1877,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 + HEAD From dde3bad90687ce2184e2930aee39b29fe5c3cb30 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 7 Feb 2022 15:18:45 -0500 Subject: [PATCH 0699/1254] Defend against null exception. #8148 --- .../DSpaceApiExceptionControllerAdvice.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 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 cd740e17a0..78f7383d61 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 @@ -222,10 +222,18 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH log.error("{} (status:{})", message, statusCode, ex); } else if (HttpStatus.valueOf(statusCode).is4xxClientError()) { // Log the error as a single-line WARN - StackTraceElement[] trace = ex.getStackTrace(); - String location = trace.length <= 0 ? "unknown" : trace[0].toString(); + String location; + String exceptionMessage; + if (null == ex) { + exceptionMessage = "none"; + location = "unknown"; + } else { + exceptionMessage = ex.getMessage(); + StackTraceElement[] trace = ex.getStackTrace(); + location = trace.length <= 0 ? "unknown" : trace[0].toString(); + } log.warn("{} (status:{} exception: {} at: {})", message, statusCode, - ex.getMessage(), location); + exceptionMessage, location); } //Exception properties will be set by org.springframework.boot.web.support.ErrorPageFilter From dcd434ded039189e68d17659d5bd82bfe4dbd65d Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Mon, 7 Feb 2022 23:13:35 +0100 Subject: [PATCH 0700/1254] Remove unneccessary second Context in RDFConsumer fixes #8152 --- .../main/java/org/dspace/rdf/RDFConsumer.java | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/rdf/RDFConsumer.java b/dspace-api/src/main/java/org/dspace/rdf/RDFConsumer.java index 76ae0cd2d2..34ab572d1b 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/RDFConsumer.java +++ b/dspace-api/src/main/java/org/dspace/rdf/RDFConsumer.java @@ -286,51 +286,54 @@ public class RDFConsumer implements Consumer { @Override public void end(Context ctx) throws Exception { log.debug("Started processing of queued events."); - // create a new context, to be sure to work as anonymous user - // we don't want to store private data in a triplestore with public - // SPARQL endpoint. - ctx = new Context(Context.Mode.READ_ONLY); - if (toDelete == null) { - log.debug("Deletion queue does not exists, creating empty queue."); - this.toDelete = new LinkedList<>(); - } - if (toConvert != null) { - log.debug("Starting conversion of DSpaceObjects."); + // store the context mode, set context read only for performance reasons, and restore the old mode + Context.Mode oldMode = ctx.getCurrentMode(); + try { + ctx.setMode(Context.Mode.READ_ONLY); + if (toDelete == null) { + log.debug("Deletion queue does not exists, creating empty queue."); + this.toDelete = new LinkedList<>(); + } + if (toConvert != null) { + log.debug("Starting conversion of DSpaceObjects."); + while (true) { + DSOIdentifier id; + try { + id = toConvert.removeFirst(); + } catch (NoSuchElementException ex) { + break; + } + + if (toDelete.contains(id)) { + log.debug("Skipping " + Constants.typeText[id.type] + " " + + id.id.toString() + " as it is marked for " + + "deletion as well."); + continue; + } + log.debug("Converting " + Constants.typeText[id.type] + " " + + id.id.toString() + "."); + convert(ctx, id); + } + log.debug("Conversion ended."); + } + log.debug("Starting to delete data from the triple store..."); while (true) { DSOIdentifier id; try { - id = toConvert.removeFirst(); + id = toDelete.removeFirst(); } catch (NoSuchElementException ex) { break; } - if (toDelete.contains(id)) { - log.debug("Skipping " + Constants.typeText[id.type] + " " - + id.id.toString() + " as it is marked for " - + "deletion as well."); - continue; - } - log.debug("Converting " + Constants.typeText[id.type] + " " + log.debug("Going to delete data from " + + Constants.typeText[id.type] + " " + id.id.toString() + "."); - convert(ctx, id); + delete(ctx, id); } - log.debug("Conversion ended."); + } finally { + // restore context mode + ctx.setMode(oldMode); } - log.debug("Starting to delete data from the triple store..."); - while (true) { - DSOIdentifier id; - try { - id = toDelete.removeFirst(); - } catch (NoSuchElementException ex) { - break; - } - - log.debug("Going to delete data from " + - Constants.typeText[id.type] + " " - + id.id.toString() + "."); - delete(ctx, id); - } - ctx.abort(); log.debug("Deletion finished."); } From ab9133fb7a422fa0030743d27ac91599d77a8371 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 8 Feb 2022 14:46:01 -0500 Subject: [PATCH 0701/1254] Make the list of ERROR HTTP status codes configurable. #8148 --- .../DSpaceApiExceptionControllerAdvice.java | 16 ++++++++++++++-- .../spring/spring-dspace-core-services.xml | 12 +++++++++++- 2 files changed, 25 insertions(+), 3 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 78f7383d61..cefdc97b76 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 @@ -13,6 +13,8 @@ import java.io.IOException; import java.sql.SQLException; import java.util.Objects; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Named; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -51,12 +53,22 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep */ @ControllerAdvice public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler { - private static final Logger log = LogManager.getLogger(DSpaceApiExceptionControllerAdvice.class); + private static final Logger log = LogManager.getLogger(); /** * Set of HTTP error codes to log as ERROR with full stack trace. */ - private static final Set LOG_AS_ERROR = Set.of(422); + private final Set LOG_AS_ERROR; + + /** + * @param statuses HTTP status codes to be logged as ERROR, with stack trace. + */ + @Inject + public DSpaceApiExceptionControllerAdvice( + @Named("org.dspace.app.rest.StackTracedHttpStatuses") + Set statuses) { + LOG_AS_ERROR = statuses; + } @ExceptionHandler({AuthorizeException.class, RESTAuthorizationException.class, AccessDeniedException.class}) protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex) diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index ce9b3ab2a2..b8b4ee3871 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -9,9 +9,12 @@ --> + 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"> @@ -125,4 +128,11 @@ + + + + HTTP status codes to be logged at ERROR level with stack trace. + + 422 + From f05c6ac1404ea43166cddb4bb33ffaf22b5e6db4 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Wed, 9 Feb 2022 14:09:32 +0100 Subject: [PATCH 0702/1254] Add call to setMetadataModified() when the name of group is changed to trigger MODIFY_METADATA event --- dspace-api/src/main/java/org/dspace/eperson/Group.java | 1 + 1 file changed, 1 insertion(+) 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 22dc20526b..b2d3964895 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java @@ -201,6 +201,7 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport { void setName(String name) throws SQLException { if (!StringUtils.equals(this.name, name) && !isPermanent()) { this.name = name; + setMetadataModified(); } } From 35179b535e53a9fc3b6b92d1df14bd03a515114b Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 10 Feb 2022 16:11:07 +0100 Subject: [PATCH 0703/1254] [CST-5251] Improved builder's cleanup to avoid consumers executions --- .../src/test/java/org/dspace/builder/BitstreamBuilder.java | 1 + .../src/test/java/org/dspace/builder/BitstreamFormatBuilder.java | 1 + dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java | 1 + .../src/test/java/org/dspace/builder/ClaimedTaskBuilder.java | 1 + .../src/test/java/org/dspace/builder/CollectionBuilder.java | 1 + .../src/test/java/org/dspace/builder/CommunityBuilder.java | 1 + dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java | 1 + .../src/test/java/org/dspace/builder/EntityTypeBuilder.java | 1 + dspace-api/src/test/java/org/dspace/builder/GroupBuilder.java | 1 + dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java | 1 + .../src/test/java/org/dspace/builder/MetadataFieldBuilder.java | 1 + .../src/test/java/org/dspace/builder/MetadataSchemaBuilder.java | 1 + dspace-api/src/test/java/org/dspace/builder/PoolTaskBuilder.java | 1 + dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java | 1 + .../src/test/java/org/dspace/builder/RelationshipBuilder.java | 1 + .../test/java/org/dspace/builder/RelationshipTypeBuilder.java | 1 + .../src/test/java/org/dspace/builder/ResourcePolicyBuilder.java | 1 + .../src/test/java/org/dspace/builder/WorkflowItemBuilder.java | 1 + .../src/test/java/org/dspace/builder/WorkspaceItemBuilder.java | 1 + 19 files changed, 19 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index f98befe57f..283091778e 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -206,6 +206,7 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { @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 bitstream = c.reloadEntity(bitstream); diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamFormatBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamFormatBuilder.java index 1051712326..a13783ceef 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamFormatBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamFormatBuilder.java @@ -34,6 +34,7 @@ public class BitstreamFormatBuilder extends AbstractCRUDBuilder @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 bitstreamFormat = c.reloadEntity(bitstreamFormat); diff --git a/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java index 614cd54c6d..1776921ac6 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BundleBuilder.java @@ -55,6 +55,7 @@ public class BundleBuilder extends AbstractDSpaceObjectBuilder { @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 bundle = c.reloadEntity(bundle); diff --git a/dspace-api/src/test/java/org/dspace/builder/ClaimedTaskBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ClaimedTaskBuilder.java index 63c03c4a91..aed712f2d2 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ClaimedTaskBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ClaimedTaskBuilder.java @@ -124,6 +124,7 @@ public class ClaimedTaskBuilder extends AbstractBuilder { @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 collection = c.reloadEntity(collection); diff --git a/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java index 5ba36af8f4..a01aef8498 100644 --- a/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CommunityBuilder.java @@ -116,6 +116,7 @@ public class CommunityBuilder extends AbstractDSpaceObjectBuilder { @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 community = c.reloadEntity(community); diff --git a/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java b/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java index 2010aef2c1..c6c1efd461 100644 --- a/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/EPersonBuilder.java @@ -32,6 +32,7 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder { @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 ePerson = c.reloadEntity(ePerson); diff --git a/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java index ef3c840bc2..36d9654adf 100644 --- a/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/EntityTypeBuilder.java @@ -36,6 +36,7 @@ public class EntityTypeBuilder extends AbstractBuilder { @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 group = c.reloadEntity(group); 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 f00104014d..aad0e86b1e 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -209,6 +209,7 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { @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 item = c.reloadEntity(item); diff --git a/dspace-api/src/test/java/org/dspace/builder/MetadataFieldBuilder.java b/dspace-api/src/test/java/org/dspace/builder/MetadataFieldBuilder.java index dfc9112a3f..52acf9d5ed 100644 --- a/dspace-api/src/test/java/org/dspace/builder/MetadataFieldBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/MetadataFieldBuilder.java @@ -38,6 +38,7 @@ public class MetadataFieldBuilder extends AbstractBuilder @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 workspaceItem = c.reloadEntity(workspaceItem); diff --git a/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java index 6970cd57c3..981ce63493 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ProcessBuilder.java @@ -60,6 +60,7 @@ public class ProcessBuilder extends AbstractBuilder { @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 process = c.reloadEntity(process); diff --git a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java index 8746033419..a5a81524a5 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java @@ -39,6 +39,7 @@ public class RelationshipBuilder extends AbstractBuilder Date: Thu, 10 Feb 2022 12:06:05 -0800 Subject: [PATCH 0704/1254] Removes session dependency when accessing special groups. --- .../authenticate/ShibAuthentication.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java index a913d27d62..200abef08d 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java @@ -235,7 +235,6 @@ public class ShibAuthentication implements AuthenticationMethod { // Step 4: Log the user in. context.setCurrentUser(eperson); - request.setAttribute("shib.authenticated", true); AuthenticateServiceFactory.getInstance().getAuthenticationService().initEPerson(context, request, eperson); log.info(eperson.getEmail() + " has been authenticated via shibboleth."); @@ -290,20 +289,13 @@ public class ShibAuthentication implements AuthenticationMethod { try { // User has not successfuly authenticated via shibboleth. if (request == null || - context.getCurrentUser() == null || - request.getSession().getAttribute("shib.authenticated") == null) { + context.getCurrentUser() == null) { return Collections.EMPTY_LIST; } - // If we have already calculated the special groups then return them. - if (request.getSession().getAttribute("shib.specialgroup") != null) { + if (context.getSpecialGroups().size() > 0 ) { log.debug("Returning cached special groups."); - List sessionGroupIds = (List) request.getSession().getAttribute("shib.specialgroup"); - List result = new ArrayList<>(); - for (UUID uuid : sessionGroupIds) { - result.add(groupService.find(context, uuid)); - } - return result; + return context.getSpecialGroups(); } log.debug("Starting to determine special groups"); @@ -401,11 +393,8 @@ public class ShibAuthentication implements AuthenticationMethod { groupIds.add(group.getID()); } - // Cache the special groups, so we don't have to recalculate them again - // for this session. - request.setAttribute("shib.specialgroup", groupIds); - return new ArrayList<>(groups); + } catch (Throwable t) { log.error("Unable to validate any sepcial groups this user may belong too because of an exception.", t); return Collections.EMPTY_LIST; From 1c2200aacdd8e975458b299bf9cc486b31f85c6f Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 10 Feb 2022 14:41:33 -0800 Subject: [PATCH 0705/1254] Added required request attribute. --- .../java/org/dspace/authenticate/ShibAuthentication.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java index 200abef08d..dba5de90f3 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java @@ -20,7 +20,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -235,6 +234,7 @@ public class ShibAuthentication implements AuthenticationMethod { // Step 4: Log the user in. context.setCurrentUser(eperson); + request.setAttribute("shib.authenticated", true); AuthenticateServiceFactory.getInstance().getAuthenticationService().initEPerson(context, request, eperson); log.info(eperson.getEmail() + " has been authenticated via shibboleth."); @@ -388,11 +388,6 @@ public class ShibAuthentication implements AuthenticationMethod { log.info("Added current EPerson to special groups: " + groups); - List groupIds = new ArrayList<>(); - for (Group group : groups) { - groupIds.add(group.getID()); - } - return new ArrayList<>(groups); } catch (Throwable t) { From eaac8a6324e22cf6634925a43a960312aab89f9f Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 11 Feb 2022 10:43:56 +0100 Subject: [PATCH 0706/1254] 87384: Fix Null metadata values being added #2 --- .../content/DSpaceObjectServiceImpl.java | 91 +++++++++---------- .../rest/WorkspaceItemRestRepositoryIT.java | 71 +++++++++++++++ 2 files changed, 115 insertions(+), 47 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index c34291c3dd..34b28d31b8 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -257,67 +257,64 @@ public abstract class DSpaceObjectServiceImpl implements boolean authorityControlled = metadataAuthorityService.isAuthorityControlled(metadataField); boolean authorityRequired = metadataAuthorityService.isAuthorityRequired(metadataField); - List newMetadata = new ArrayList<>(values.size()); + List newMetadata = new ArrayList<>(); // We will not verify that they are valid entries in the registry // until update() is called. for (int i = 0; i < values.size(); i++) { - - if (authorities != null && authorities.size() >= i) { - if (StringUtils.startsWith(authorities.get(i), Constants.VIRTUAL_AUTHORITY_PREFIX)) { - continue; - } - } - MetadataValue metadataValue = metadataValueService.create(context, dso, metadataField); - newMetadata.add(metadataValue); - - metadataValue.setPlace(placeSupplier.get()); - - metadataValue.setLanguage(lang == null ? null : lang.trim()); - - // Logic to set Authority and Confidence: - // - normalize an empty string for authority to NULL. - // - if authority key is present, use given confidence or NOVALUE if not given - // - otherwise, preserve confidence if meaningful value was given since it may document a failed - // authority lookup - // - CF_UNSET signifies no authority nor meaningful confidence. - // - it's possible to have empty authority & CF_ACCEPTED if e.g. user deletes authority key - if (authorityControlled) { - if (authorities != null && authorities.get(i) != null && authorities.get(i).length() > 0) { - metadataValue.setAuthority(authorities.get(i)); - metadataValue.setConfidence(confidences == null ? Choices.CF_NOVALUE : confidences.get(i)); - } else { - metadataValue.setAuthority(null); - metadataValue.setConfidence(confidences == null ? Choices.CF_UNSET : confidences.get(i)); - } - // authority sanity check: if authority is required, was it supplied? - // XXX FIXME? can't throw a "real" exception here without changing all the callers to expect it, so - // use a runtime exception - if (authorityRequired && (metadataValue.getAuthority() == null || metadataValue.getAuthority() - .length() == 0)) { - throw new IllegalArgumentException("The metadata field \"" + metadataField - .toString() + "\" requires an authority key but none was provided. Value=\"" + values - .get(i) + "\""); - } - } if (values.get(i) != null) { + if (authorities != null && authorities.size() >= i) { + if (StringUtils.startsWith(authorities.get(i), Constants.VIRTUAL_AUTHORITY_PREFIX)) { + continue; + } + } + MetadataValue metadataValue = metadataValueService.create(context, dso, metadataField); + newMetadata.add(metadataValue); + + metadataValue.setPlace(placeSupplier.get()); + + metadataValue.setLanguage(lang == null ? null : lang.trim()); + + // Logic to set Authority and Confidence: + // - normalize an empty string for authority to NULL. + // - if authority key is present, use given confidence or NOVALUE if not given + // - otherwise, preserve confidence if meaningful value was given since it may document a failed + // authority lookup + // - CF_UNSET signifies no authority nor meaningful confidence. + // - it's possible to have empty authority & CF_ACCEPTED if e.g. user deletes authority key + if (authorityControlled) { + if (authorities != null && authorities.get(i) != null && authorities.get(i).length() > 0) { + metadataValue.setAuthority(authorities.get(i)); + metadataValue.setConfidence(confidences == null ? Choices.CF_NOVALUE : confidences.get(i)); + } else { + metadataValue.setAuthority(null); + metadataValue.setConfidence(confidences == null ? Choices.CF_UNSET : confidences.get(i)); + } + // authority sanity check: if authority is required, was it supplied? + // XXX FIXME? can't throw a "real" exception here without changing all the callers to expect it, so + // use a runtime exception + if (authorityRequired && (metadataValue.getAuthority() == null || metadataValue.getAuthority() + .length() == 0)) { + throw new IllegalArgumentException("The metadata field \"" + metadataField + .toString() + "\" requires an authority key but none was provided. Value=\"" + values + .get(i) + "\""); + } + } // remove control unicode char String temp = values.get(i).trim(); char[] dcvalue = temp.toCharArray(); for (int charPos = 0; charPos < dcvalue.length; charPos++) { if (Character.isISOControl(dcvalue[charPos]) && - !String.valueOf(dcvalue[charPos]).equals("\u0009") && - !String.valueOf(dcvalue[charPos]).equals("\n") && - !String.valueOf(dcvalue[charPos]).equals("\r")) { + !String.valueOf(dcvalue[charPos]).equals("\u0009") && + !String.valueOf(dcvalue[charPos]).equals("\n") && + !String.valueOf(dcvalue[charPos]).equals("\r")) { dcvalue[charPos] = ' '; } } metadataValue.setValue(String.valueOf(dcvalue)); - } else { - metadataValue.setValue(null); - } - //An update here isn't needed, this is persited upon the merge of the owning object + //An update here isn't needed, this is persited upon the merge of the owning object // metadataValueService.update(context, metadataValue); - dso.addDetails(metadataField.toString()); + dso.addDetails(metadataField.toString()); + } } setMetadataModified(dso); return newMetadata; 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 ffaf6f2da7..5fa1bbbf64 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 @@ -37,6 +37,7 @@ import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.matchers.JsonPathMatchers; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.time.DateUtils; @@ -2494,6 +2495,76 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration ; } + @Test + /** + * Test the addition of metadata of a null value + * + * @throws Exception + */ + public void patchAddMetadataNullValueTest() 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(); + String authToken = getAuthToken(eperson.getEmail(), password); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + + context.restoreAuthSystemState(); + + // try to add the title + List operations = new ArrayList(); + // create a list of values to use in add operation + List> titelValues = new ArrayList>(); + List> uriValues = new ArrayList>(); + Map value = new HashMap(); + Map value2 = new HashMap(); + value.put("value", "New Title"); + value2.put("value", null); + titelValues.add(value); + uriValues.add(value2); + operations.add(new AddOperation("/sections/traditionalpageone/dc.title", titelValues)); + operations.add(new AddOperation("/sections/traditionalpageone/dc.identifier.uri", uriValues)); + + String patchBody = getPatchContent(operations); + getClient(authToken).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 if the new title if back and the other values untouched + Matchers.is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "New Title", "2017-10-17", "ExtraEntry")))) + .andExpect(jsonPath("$", JsonPathMatchers + .hasNoJsonPath("$.sections.traditionalpageone['dc.identifier.uri']"))); + + + // verify that the patch changes have been persisted + getClient(authToken).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")))) + .andExpect(jsonPath("$", JsonPathMatchers + .hasNoJsonPath("$.sections.traditionalpageone['dc.identifier.uri']"))) + ; + } + @Test public void patchAddMetadataUpdateExistValueTest() throws Exception { context.turnOffAuthorisationSystem(); From 56896c3bb6cef8e6fd4ee2a210b3909c98f6dcfa Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 11 Feb 2022 16:34:02 +0100 Subject: [PATCH 0707/1254] [CST-5251] Improved missing builder's cleanup --- .../src/test/java/org/dspace/builder/RequestItemBuilder.java | 1 + .../src/test/java/org/dspace/builder/ResourcePolicyBuilder.java | 1 + dspace-api/src/test/java/org/dspace/builder/VersionBuilder.java | 1 + 3 files changed, 3 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java index a9e4aed174..66e6245ff6 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RequestItemBuilder.java @@ -117,6 +117,7 @@ public class RequestItemBuilder throws Exception { LOG.debug("cleanup()"); try ( Context ctx = new Context(); ) { + ctx.setDispatcher("noindex"); ctx.turnOffAuthorisationSystem(); requestItem = ctx.reloadEntity(requestItem); if (null != requestItem) { diff --git a/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java index 284a6c8c9d..70b1f8d73d 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ResourcePolicyBuilder.java @@ -82,6 +82,7 @@ public class ResourcePolicyBuilder extends AbstractBuilder public void delete(Version version) throws Exception { try (Context context = new Context()) { context.turnOffAuthorisationSystem(); + context.setDispatcher("noindex"); Version attachedTab = context.reloadEntity(version); if (attachedTab != null) { getService().delete(context, attachedTab); From e4857c0b1df05d95158b86b35f8f944b0e9b4a83 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 11 Feb 2022 18:34:25 +0100 Subject: [PATCH 0708/1254] [CST-5288] Introduced spring boot actuators --- dspace-server-webapp/pom.xml | 6 +++ .../configuration/ActuatorConfiguration.java | 39 +++++++++++++++++++ .../security/WebSecurityConfiguration.java | 2 +- .../src/main/resources/application.properties | 4 ++ 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index e34d3ad1bc..6b703899ed 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -279,6 +279,12 @@ spring-boot-starter-aop ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-actuator + ${spring-boot.version} + com.flipkart.zjsonpatch diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java new file mode 100644 index 0000000000..f0d78edd2f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.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.configuration; + +import java.util.Arrays; + +import org.dspace.app.rest.DiscoverableEndpointsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; +import org.springframework.hateoas.Link; + +/** + * Configuration class related to the actuator endpoints. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@Configuration +public class ActuatorConfiguration { + + @Autowired + private DiscoverableEndpointsService discoverableEndpointsService; + + @Value("${management.endpoints.web.base-path:/actuator}") + private String actuatorBasePath; + + @EventListener(ApplicationReadyEvent.class) + public void registerActuatorEndpoints() { + discoverableEndpointsService.register(this, Arrays.asList(new Link(actuatorBasePath, "actuator"))); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index 38e8c1d7f4..4696cf9d5f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -81,7 +81,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { // Configure authentication requirements for ${dspace.server.url}/api/ URL only // NOTE: REST API is hardcoded to respond on /api/. Other modules (OAI, SWORD, IIIF, etc) use other root paths. http.requestMatchers() - .antMatchers("/api/**", "/iiif/**") + .antMatchers("/api/**", "/iiif/**", "/actuator/**") .and() // Enable Spring Security authorization on these paths .authorizeRequests() diff --git a/dspace-server-webapp/src/main/resources/application.properties b/dspace-server-webapp/src/main/resources/application.properties index c695fe2fba..789de70bcc 100644 --- a/dspace-server-webapp/src/main/resources/application.properties +++ b/dspace-server-webapp/src/main/resources/application.properties @@ -132,3 +132,7 @@ spring.servlet.multipart.max-file-size = 512MB # Maximum size of a multipart request (i.e. max total size of all files in one request) (default = 10MB) spring.servlet.multipart.max-request-size = 512MB + +#Spring Boot actuator configuration +management.endpoint.health.show-details = when-authorized +management.endpoint.health.roles = ADMIN From 980359a0a93035ac00e1d85bad91a606357f1055 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 14 Feb 2022 13:44:45 +0100 Subject: [PATCH 0709/1254] [CST-5288] Added custom health indicator to check GeoIP configuration status --- .../org/dspace/statistics/GeoIpService.java | 57 +++++++++++++++ .../statistics/SolrLoggerServiceImpl.java | 27 ++----- .../config/spring/api/solr-services.xml | 2 + .../configuration/ActuatorConfiguration.java | 18 +++++ .../app/rest/health/GeoIpHealthIndicator.java | 40 ++++++++++ .../src/main/resources/application.properties | 4 - .../dspace/app/rest/HealthIndicatorsIT.java | 72 ++++++++++++++++++ .../rest/health/GeoIpHealthIndicatorTest.java | 73 +++++++++++++++++++ .../link/search/HealthIndicatorMatcher.java | 46 ++++++++++++ dspace/config/dspace.cfg | 13 ++++ dspace/config/spring/api/solr-services.xml | 2 + 11 files changed, 329 insertions(+), 25 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/statistics/GeoIpService.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/health/GeoIpHealthIndicator.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/health/GeoIpHealthIndicatorTest.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/link/search/HealthIndicatorMatcher.java diff --git a/dspace-api/src/main/java/org/dspace/statistics/GeoIpService.java b/dspace-api/src/main/java/org/dspace/statistics/GeoIpService.java new file mode 100644 index 0000000000..7f8a11e5ba --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/statistics/GeoIpService.java @@ -0,0 +1,57 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.statistics; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import com.maxmind.geoip2.DatabaseReader; +import org.apache.commons.lang3.StringUtils; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service that handle the GeoIP database file. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class GeoIpService { + + @Autowired + private ConfigurationService configurationService; + + /** + * Returns an instance of {@link DatabaseReader} based on the configured db + * file, if any. + * + * @return the Database reader + * @throws IllegalStateException if the db file is not configured correctly + */ + public DatabaseReader getDatabaseReader() throws IllegalStateException { + String dbPath = configurationService.getProperty("usage-statistics.dbfile"); + if (StringUtils.isBlank(dbPath)) { + throw new IllegalStateException("The required 'dbfile' configuration is missing in solr-statistics.cfg!"); + } + + try { + File dbFile = new File(dbPath); + return new DatabaseReader.Builder(dbFile).build(); + } catch (FileNotFoundException fe) { + throw new IllegalStateException( + "The GeoLite Database file is missing (" + dbPath + ")! Solr Statistics cannot generate location " + + "based reports! Please see the DSpace installation instructions for instructions to install " + + "this file.",fe); + } catch (IOException e) { + throw new IllegalStateException( + "Unable to load GeoLite Database file (" + dbPath + ")! You may need to reinstall it. See the " + + "DSpace installation instructions for more details.", e); + } + } +} 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 9cc032a998..4b2ae94e75 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -8,7 +8,6 @@ package org.dspace.statistics; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; @@ -142,6 +141,8 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea private ClientInfoService clientInfoService; @Autowired private SolrStatisticsCore solrStatisticsCore; + @Autowired + private GeoIpService geoIpService; /** URL to the current-year statistics core. Prior-year shards will have a year suffixed. */ private String statisticsCoreURL; @@ -179,26 +180,10 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea //spiderIps = SpiderDetector.getSpiderIpAddresses(); DatabaseReader service = null; - // Get the db file for the location - String dbPath = configurationService.getProperty("usage-statistics.dbfile"); - if (dbPath != null) { - try { - File dbFile = new File(dbPath); - service = new DatabaseReader.Builder(dbFile).build(); - } catch (FileNotFoundException fe) { - log.error( - "The GeoLite Database file is missing (" + dbPath + ")! Solr Statistics cannot generate location " + - "based reports! Please see the DSpace installation instructions for instructions to install " + - "this file.", - fe); - } catch (IOException e) { - log.error( - "Unable to load GeoLite Database file (" + dbPath + ")! You may need to reinstall it. See the " + - "DSpace installation instructions for more details.", - e); - } - } else { - log.error("The required 'dbfile' configuration is missing in solr-statistics.cfg!"); + try { + service = geoIpService.getDatabaseReader(); + } catch (IllegalStateException ex) { + log.error(ex); } locationService = service; } diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml index 5f86c73598..32ab90b2cc 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/solr-services.xml @@ -47,5 +47,7 @@ + + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java index f0d78edd2f..cb91ae4152 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java @@ -10,9 +10,14 @@ package org.dspace.app.rest.configuration; import java.util.Arrays; import org.dspace.app.rest.DiscoverableEndpointsService; +import org.dspace.app.rest.health.GeoIpHealthIndicator; +import org.dspace.discovery.SolrSearchCore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; +import org.springframework.boot.actuate.solr.SolrHealthIndicator; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; import org.springframework.hateoas.Link; @@ -36,4 +41,17 @@ public class ActuatorConfiguration { public void registerActuatorEndpoints() { discoverableEndpointsService.register(this, Arrays.asList(new Link(actuatorBasePath, "actuator"))); } + + @Bean + @ConditionalOnEnabledHealthIndicator("solr") + public SolrHealthIndicator solrHealthIndicator(SolrSearchCore solrSearchCore) { + return new SolrHealthIndicator(solrSearchCore.getSolr()); + } + + @Bean + @ConditionalOnEnabledHealthIndicator("geoIp") + public GeoIpHealthIndicator geoIpHealthIndicator() { + return new GeoIpHealthIndicator(); + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/GeoIpHealthIndicator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/GeoIpHealthIndicator.java new file mode 100644 index 0000000000..5c8ad92dbb --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/GeoIpHealthIndicator.java @@ -0,0 +1,40 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.health; + +import org.dspace.statistics.GeoIpService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.boot.actuate.health.HealthIndicator; + +/** + * Implementation of {@link HealthIndicator} that verifies if the GeoIP database + * is configured correctly. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class GeoIpHealthIndicator extends AbstractHealthIndicator { + + @Autowired + private GeoIpService geoIpService; + + @Override + protected void doHealthCheck(Builder builder) throws Exception { + + try { + geoIpService.getDatabaseReader(); + builder.up(); + } catch (IllegalStateException ex) { + builder.outOfService().withDetail("reason", ex.getMessage()); + } + + } + +} diff --git a/dspace-server-webapp/src/main/resources/application.properties b/dspace-server-webapp/src/main/resources/application.properties index 789de70bcc..c695fe2fba 100644 --- a/dspace-server-webapp/src/main/resources/application.properties +++ b/dspace-server-webapp/src/main/resources/application.properties @@ -132,7 +132,3 @@ spring.servlet.multipart.max-file-size = 512MB # Maximum size of a multipart request (i.e. max total size of all files in one request) (default = 10MB) spring.servlet.multipart.max-request-size = 512MB - -#Spring Boot actuator configuration -management.endpoint.health.show-details = when-authorized -management.endpoint.health.roles = ADMIN diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java new file mode 100644 index 0000000000..c28e172e61 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.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; + +import static org.dspace.app.rest.link.search.HealthIndicatorMatcher.match; +import static org.dspace.app.rest.link.search.HealthIndicatorMatcher.matchDb; +import static org.hamcrest.Matchers.allOf; +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.Map; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.junit.Test; +import org.springframework.boot.actuate.health.Status; + +/** + * Integration tests to verify the health indicators configuration. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class HealthIndicatorsIT extends AbstractControllerIntegrationTest { + + private static final String HEALTH_PATH = "/actuator/health"; + + @Test + public void testWithAnonymousUser() throws Exception { + + getClient().perform(get(HEALTH_PATH)) + .andExpect(status().isServiceUnavailable()) + .andExpect(jsonPath("$.status", is(Status.OUT_OF_SERVICE.getCode()))) + .andExpect(jsonPath("$.components").doesNotExist()); + + } + + @Test + public void testWithNotAdminUser() throws Exception { + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get(HEALTH_PATH)) + .andExpect(status().isServiceUnavailable()) + .andExpect(jsonPath("$.status", is(Status.OUT_OF_SERVICE.getCode()))) + .andExpect(jsonPath("$.components").doesNotExist()); + + } + + @Test + public void testWithAdminUser() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get(HEALTH_PATH)) + .andExpect(status().isServiceUnavailable()) + .andExpect(jsonPath("$.status", is(Status.OUT_OF_SERVICE.getCode()))) + .andExpect(jsonPath("$.components", allOf( + matchDb(Status.UP), + match("solr", Status.UP, Map.of("status", 0, "detectedPathType", "root")), + match("geoIp", Status.OUT_OF_SERVICE, + Map.of("reason", "The required 'dbfile' configuration is missing in solr-statistics.cfg!")) + ))); + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/health/GeoIpHealthIndicatorTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/health/GeoIpHealthIndicatorTest.java new file mode 100644 index 0000000000..ba454a8f1e --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/health/GeoIpHealthIndicatorTest.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.health; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import com.maxmind.geoip2.DatabaseReader; +import org.dspace.statistics.GeoIpService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; + +/** + * Unit tests for {@link GeoIpHealthIndicator}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@RunWith(MockitoJUnitRunner.class) +public class GeoIpHealthIndicatorTest { + + @Mock + private GeoIpService geoIpService; + + @InjectMocks + private GeoIpHealthIndicator geoIpHealthIndicator; + + @Mock + private DatabaseReader databaseReader; + + @Test + public void testWithGeoIpConfiguredCorrectly() { + when(geoIpService.getDatabaseReader()).thenReturn(databaseReader); + + Health health = geoIpHealthIndicator.health(); + + assertThat(health.getStatus(), is(Status.UP)); + assertThat(health.getDetails(), anEmptyMap()); + } + + @Test + public void testWithGeoIpWrongConfiguration() { + when(geoIpService.getDatabaseReader()).thenThrow(new IllegalStateException("Missing db file")); + + Health health = geoIpHealthIndicator.health(); + + assertThat(health.getStatus(), is(Status.OUT_OF_SERVICE)); + assertThat(health.getDetails(), is(Map.of("reason", "Missing db file"))); + } + + @Test + public void testWithUnexpectedError() { + when(geoIpService.getDatabaseReader()).thenThrow(new RuntimeException("Generic error")); + + Health health = geoIpHealthIndicator.health(); + + assertThat(health.getStatus(), is(Status.DOWN)); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/link/search/HealthIndicatorMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/link/search/HealthIndicatorMatcher.java new file mode 100644 index 0000000000..d2ee1e2c70 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/link/search/HealthIndicatorMatcher.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.link.search; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import java.util.Map; + +import org.hamcrest.Matcher; +import org.springframework.boot.actuate.health.Status; + +/** + * Matcher for the health indicators. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public final class HealthIndicatorMatcher { + + private HealthIndicatorMatcher() { + + } + + public static Matcher matchDb(Status status) { + return allOf( + hasJsonPath("$.db"), + hasJsonPath("$.db.status", is(status.getCode())), + hasJsonPath("$.db.components", allOf( + match("dspaceDataSource", status, Map.of("database", "H2", "result", 1, "validationQuery", "SELECT 1")), + match("dataSource", status, Map.of("database", "H2", "result", 1, "validationQuery", "SELECT 1"))))); + } + + public static Matcher match(String name, Status status, Map details) { + return allOf( + hasJsonPath("$." + name), + hasJsonPath("$." + name + ".status", is(status.getCode())), + hasJsonPath("$." + name + ".details", is(details))); + } +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 02c618abf4..dcd4dc8e11 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1553,6 +1553,19 @@ mail.helpdesk.name = Help Desk request.item.helpdesk.override = false +#---------------------------------------------------------------# +#------------SPRING BOOT ACTUATOR CONFIGURATION-----------------# +#---------------------------------------------------------------# + +management.endpoint.health.show-details = when-authorized +management.endpoint.health.roles = ADMIN + +management.health.ping.enabled = false +management.health.diskSpace.enabled = false + +info.app.name = ${dspace.name} + + #------------------------------------------------------------------# #-------------------MODULE CONFIGURATIONS--------------------------# #------------------------------------------------------------------# diff --git a/dspace/config/spring/api/solr-services.xml b/dspace/config/spring/api/solr-services.xml index 698a824184..80e9449d4c 100644 --- a/dspace/config/spring/api/solr-services.xml +++ b/dspace/config/spring/api/solr-services.xml @@ -31,5 +31,7 @@ + + From a6fa1f17f70a6745f3ffd34b94654fa0e2b5a113 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 14 Feb 2022 16:35:54 +0100 Subject: [PATCH 0710/1254] [CST-5288] Made the actuator security configuration dynamic --- .../dspace/app/rest/security/WebSecurityConfiguration.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index 4696cf9d5f..d054fcd9e7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -11,6 +11,7 @@ import org.dspace.app.rest.exception.DSpaceAccessDeniedHandler; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -63,6 +64,9 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private DSpaceAccessDeniedHandler accessDeniedHandler; + @Value("${management.endpoints.web.base-path:/actuator}") + private String actuatorBasePath; + @Override public void configure(WebSecurity webSecurity) throws Exception { // Define URL patterns which Spring Security will ignore entirely. @@ -81,7 +85,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { // Configure authentication requirements for ${dspace.server.url}/api/ URL only // NOTE: REST API is hardcoded to respond on /api/. Other modules (OAI, SWORD, IIIF, etc) use other root paths. http.requestMatchers() - .antMatchers("/api/**", "/iiif/**", "/actuator/**") + .antMatchers("/api/**", "/iiif/**", actuatorBasePath + "/**") .and() // Enable Spring Security authorization on these paths .authorizeRequests() From f058d123cfd20aceb8dc0dcf5d2cdb7614dfdf68 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 14 Feb 2022 12:49:32 -0600 Subject: [PATCH 0711/1254] Update Docker compose scripts to point at new Entities data & do a full reindex after loading assetstore. --- dspace/src/main/docker-compose/cli.assetstore.yml | 2 +- dspace/src/main/docker-compose/db.entities.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace/src/main/docker-compose/cli.assetstore.yml b/dspace/src/main/docker-compose/cli.assetstore.yml index 7773facae6..d6a6a4395f 100644 --- a/dspace/src/main/docker-compose/cli.assetstore.yml +++ b/dspace/src/main/docker-compose/cli.assetstore.yml @@ -24,6 +24,6 @@ services: tar xvfz /tmp/assetstore.tar.gz fi - /dspace/bin/dspace index-discovery + /dspace/bin/dspace index-discovery -b /dspace/bin/dspace oai import /dspace/bin/dspace oai clean-cache diff --git a/dspace/src/main/docker-compose/db.entities.yml b/dspace/src/main/docker-compose/db.entities.yml index 762519f508..8d86f7bb83 100644 --- a/dspace/src/main/docker-compose/db.entities.yml +++ b/dspace/src/main/docker-compose/db.entities.yml @@ -13,7 +13,7 @@ services: image: dspace/dspace-postgres-pgcrypto:loadsql environment: # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-2021-04-14.sql + - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql dspace: ### OVERRIDE default 'entrypoint' in 'docker-compose.yml #### # Ensure that the database is ready BEFORE starting tomcat From 8ca0a1f2f7ab31ca2a3a2929079c6952e9e6af31 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 14 Feb 2022 14:09:33 -0500 Subject: [PATCH 0712/1254] Move configuration from Spring to 'dspace.cfg'. #8148 --- .../DSpaceApiExceptionControllerAdvice.java | 38 ++++++++++++------- .../spring/spring-dspace-core-services.xml | 12 +----- dspace/config/dspace.cfg | 10 +++-- 3 files changed, 32 insertions(+), 28 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 cefdc97b76..70bc2c7eed 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 @@ -11,10 +11,10 @@ import static org.springframework.web.servlet.DispatcherServlet.EXCEPTION_ATTRIB import java.io.IOException; import java.sql.SQLException; +import java.util.HashSet; import java.util.Objects; import java.util.Set; import javax.inject.Inject; -import javax.inject.Named; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; import org.springframework.beans.TypeMismatchException; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.repository.support.QueryMethodParameterConversionException; @@ -56,19 +57,15 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH private static final Logger log = LogManager.getLogger(); /** - * Set of HTTP error codes to log as ERROR with full stack trace. + * Default collection of HTTP error codes to log as ERROR with full stack trace. */ - private final Set LOG_AS_ERROR; + private static final String[] LOG_AS_ERROR_DEFAULT = { "422" }; + + /** Configuration parameter for ERROR treatment. */ + private static final String P_LOG_AS_ERROR = "logging.server.include-stacktrace-for-httpcode"; - /** - * @param statuses HTTP status codes to be logged as ERROR, with stack trace. - */ @Inject - public DSpaceApiExceptionControllerAdvice( - @Named("org.dspace.app.rest.StackTracedHttpStatuses") - Set statuses) { - LOG_AS_ERROR = statuses; - } + private ConfigurationService configurationService; @ExceptionHandler({AuthorizeException.class, RESTAuthorizationException.class, AccessDeniedException.class}) protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex) @@ -212,8 +209,10 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH * Send the error to the response. * 5xx errors will be logged as ERROR with a full stack trace. 4xx errors * will be logged as WARN without a stack trace. Specific 4xx errors where - * an ERROR log with full stack trace is more appropriate are configured in - * {@link #LOG_AS_ERROR} + * an ERROR log with full stack trace is more appropriate are configured + * using property {@code logging.server.include-stacktrace-for-httpcode} + * (see {@link P_LOG_AS_ERROR} and {@link LOG_AS_ERROR_DEFAULT}). + * * @param request current request * @param response current response * @param ex Exception thrown @@ -228,6 +227,19 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH //Make sure Spring picks up this exception request.setAttribute(EXCEPTION_ATTRIBUTE, ex); + // Which status codes should be treated as ERROR? + final Set LOG_AS_ERROR = new HashSet<>(); + String[] error_codes = configurationService.getArrayProperty( + P_LOG_AS_ERROR, LOG_AS_ERROR_DEFAULT); + for (String code : error_codes) { + try { + LOG_AS_ERROR.add(Integer.valueOf(code)); + } catch (NumberFormatException e) { + log.warn("Non-integer HTTP status code {} in {}", code, P_LOG_AS_ERROR); + // And continue + } + } + // We don't want to fill logs with bad/invalid REST API requests. if (HttpStatus.valueOf(statusCode).is5xxServerError() || LOG_AS_ERROR.contains(statusCode)) { // Log the full error and status code diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index b8b4ee3871..ce9b3ab2a2 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -9,12 +9,9 @@ --> + http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> @@ -128,11 +125,4 @@ - - - - HTTP status codes to be logged at ERROR level with stack trace. - - 422 - diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 03865bafc3..ae85943d00 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -216,12 +216,14 @@ mail.message.headers = charset ##### Logging configuration ##### # Main logging settings can be found in config/log4j2.xml -# below some configuration properties for the server webapp that can be modified at runtime +# Below are some configuration properties for the server webapp that can be +# modified at runtime. logging.server.include-after-request = false -logging.server.include-payload = false -logging.server.include-headers = false -logging.server.include-query-string = false logging.server.include-client-info = false +logging.server.include-headers = false +logging.server.include-payload = false +logging.server.include-query-string = false +logging.server.include-stacktrace-for-httpcode = 422 logging.server.max-payload-length = 10000 ##### DOI registration agency credentials ###### From 8c44a95c7b038c0422447cd2c713b0cfd299c9dd Mon Sep 17 00:00:00 2001 From: Huma Zafar Date: Sat, 12 Feb 2022 14:49:27 -0500 Subject: [PATCH 0713/1254] Adds check for whether source and target collections are the same when moving an item. (#8055) --- .../org/dspace/content/ItemServiceImpl.java | 6 +++++ .../java/org/dspace/content/ItemTest.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) 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 96dac1a4df..dbde9745fb 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -911,6 +911,12 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Override public void move(Context context, Item item, Collection from, Collection to) throws SQLException, AuthorizeException, IOException { + + // If the two collections are the same, do nothing. + if (from.equals(to)) { + return; + } + // Use the normal move method, and default to not inherit permissions this.move(context, item, from, to, false); } diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index 6af1cd5e02..9558b1bb40 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -19,6 +19,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.File; @@ -41,6 +43,7 @@ import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.core.Constants; @@ -1410,6 +1413,27 @@ public class ItemTest extends AbstractDSpaceObjectTest { assertThat("testMove 1", it.getOwningCollection(), equalTo(to)); } + /** + * Test of move method, of class Item, where both Collections are the same. + */ + @Test + public void testMoveSameCollection() throws Exception { + context.turnOffAuthorisationSystem(); + while (it.getCollections().size() > 1) { + it.removeCollection(it.getCollections().get(0)); + } + + Collection collection = it.getCollections().get(0); + it.setOwningCollection(collection); + ItemService itemServiceSpy = spy(itemService); + + itemService.move(context, it, collection, collection); + context.restoreAuthSystemState(); + assertThat("testMoveSameCollection 0", it.getOwningCollection(), notNullValue()); + assertThat("testMoveSameCollection 1", it.getOwningCollection(), equalTo(collection)); + verify(itemServiceSpy, times(0)).delete(context, it); + } + /** * Test of hasUploadedFiles method, of class Item. */ From c4a6fcbf198e038caacabb1ba61f32d3e46c3d03 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 15 Feb 2022 13:25:36 -0500 Subject: [PATCH 0714/1254] Remove outdated exclusion. #8099 Also add some missing direct dependencies. I thought these were causing a problem, which in fact came from an old version of httpcore, but we need these anyway. --- dspace-api/pom.xml | 35 +++++++++++++++++++++-------------- pom.xml | 7 ++++++- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 8a81c8574c..b3ffcd1be8 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -338,20 +338,19 @@ - org.hibernate - hibernate-core - - - - org.javassist - javassist - - - - net.bytebuddy - byte-buddy - - + org.apache.logging.log4j + log4j-api + + + org.hibernate + hibernate-core + + + + org.javassist + javassist + + org.hibernate @@ -595,6 +594,14 @@ org.apache.httpcomponents httpclient + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpmime + org.apache.solr diff --git a/pom.xml b/pom.xml index cd930e70ed..581b84e3b1 100644 --- a/pom.xml +++ b/pom.xml @@ -1605,13 +1605,18 @@ org.apache.httpcomponents httpcore - 4.4.4 + 4.4.15 org.apache.httpcomponents httpclient 4.5.13 + + org.apache.httpcomponents + httpmime + 4.5.13 + org.slf4j jcl-over-slf4j From 8debe3cb606583325933b1187f936a06776dfa9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Feb 2022 01:19:23 +0000 Subject: [PATCH 0715/1254] Bump postgresql from 42.2.25 to 42.3.3 Bumps [postgresql](https://github.com/pgjdbc/pgjdbc) from 42.2.25 to 42.3.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.2.25...REL42.3.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 6dbc35d8ac..27a07e6596 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.2.2.RELEASE 5.4.10.Final 6.0.18.Final - 42.2.25 + 42.3.3 8.8.1 1.2.22 From 42233cbfe041e3bad08e09f2f355e8d891bdbb64 Mon Sep 17 00:00:00 2001 From: Martin Walk Date: Wed, 16 Feb 2022 16:48:33 +0100 Subject: [PATCH 0716/1254] Fix DSpace/DSpace#8167 XSD for controlled vocabulary Require attribute id for nodes. --- dspace/config/controlled-vocabularies/controlledvocabulary.xsd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/controlled-vocabularies/controlledvocabulary.xsd b/dspace/config/controlled-vocabularies/controlledvocabulary.xsd index 7a5defefbd..5863d5d560 100644 --- a/dspace/config/controlled-vocabularies/controlledvocabulary.xsd +++ b/dspace/config/controlled-vocabularies/controlledvocabulary.xsd @@ -56,7 +56,7 @@ or refer to the Web site http://dspace-dev.dsi.uminho.pt. - + From 6bea8e83b464fd1e9a09bb34d04b1e6b731774d8 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 17 Feb 2022 17:15:57 +0100 Subject: [PATCH 0717/1254] 87624: IT demonstrating issue with ADD patch on RP.endDate --- .../rest/ResourcePolicyRestRepositoryIT.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index 5b3b70a70d..9651a9178c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -1353,6 +1353,65 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio hasJsonPath("$.startDate", is(formatDate.format(newDate)))))); } + @Test + public void patchAddEndDataTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson eperson1 = EPersonBuilder.createEPerson(context) + .withEmail("eperson1@mail.com") + .withPassword("qwerty01") + .build(); + + Community community = CommunityBuilder.createCommunity(context).build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withAdminGroup(eperson1) + .build(); + + Item publicItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + ResourcePolicy resourcePolicy = ResourcePolicyBuilder.createResourcePolicy(context) + .withAction(Constants.READ) + .withDspaceObject(publicItem1) + .withGroup( + EPersonServiceFactory.getInstance().getGroupService() + .findByName(context, + Group.ANONYMOUS)) + .withPolicyType(ResourcePolicy.TYPE_CUSTOM) + .build(); + + context.restoreAuthSystemState(); + + Calendar newCalendar = Calendar.getInstance(); + SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd"); + Date newDate = new Date(); + + List ops = new ArrayList(); + AddOperation addOperation = new AddOperation("/endDate", formatDate.format(newDate)); + ops.add(addOperation); + String patchBody = getPatchContent(ops); + + String authToken = getAuthToken(eperson1.getEmail(), "qwerty01"); + getClient(authToken).perform(patch("/api/authz/resourcepolicies/" + resourcePolicy.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.name", is(resourcePolicy.getRpName())), + hasJsonPath("$.description", is(resourcePolicy.getRpDescription())), + hasJsonPath("$.action", is(Constants.actionText[resourcePolicy.getAction()])), + hasJsonPath("$.startDate", is(resourcePolicy.getStartDate())), + hasJsonPath("$.endDate", is(formatDate.format(newDate)))))); + + getClient(authToken).perform(get("/api/authz/resourcepolicies/" + resourcePolicy.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.action", is(Constants.actionText[resourcePolicy.getAction()])), + hasJsonPath("$.endDate", is(formatDate.format(newDate)))))); + } + @Test public void patchRemoveStartDataTest() throws Exception { context.turnOffAuthorisationSystem(); From 6790ee157e03bb035c6a5affce763780505a4ec0 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 17 Feb 2022 18:18:03 +0100 Subject: [PATCH 0718/1254] 87624: Fix issue with ADD patch on RP.endDate --- .../resourcePolicy/ResourcePolicyEndDateAddOperation.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateAddOperation.java index 54572614c9..b06637bad2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateAddOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/resourcePolicy/ResourcePolicyEndDateAddOperation.java @@ -43,7 +43,6 @@ public class ResourcePolicyEndDateAddOperation extends PatchOperation { checkOperationValue(operation.getValue()); if (this.supports(resource, operation)) { ResourcePolicy resourcePolicy = (ResourcePolicy) resource; - resourcePolicyUtils.checkResourcePolicyForExistingEndDateValue(resourcePolicy, operation); resourcePolicyUtils.checkResourcePolicyForConsistentEndDateValue(resourcePolicy, operation); this.add(resourcePolicy, operation); return resource; From 73b927d663431cb560d55c539f5ec8c887d83162 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 21 Feb 2022 09:52:19 -0500 Subject: [PATCH 0719/1254] Document that more than one HTTP status can be configured for tracing. #8148 --- dspace/config/dspace.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index ae85943d00..e794e57fbe 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -223,6 +223,8 @@ logging.server.include-client-info = false logging.server.include-headers = false logging.server.include-payload = false logging.server.include-query-string = false +# include-stacktrace-for-httpcode accepts multiple values, comma-separated or by +# repeating the assignment logging.server.include-stacktrace-for-httpcode = 422 logging.server.max-payload-length = 10000 From e261e1ce4340af3dc89d31e083a6fdf8d61a1610 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 21 Feb 2022 09:59:14 -0500 Subject: [PATCH 0720/1254] Add core prefix property to local.cfg.EXAMPLE #8129 --- dspace/config/local.cfg.EXAMPLE | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index f8a931cd25..c0d5e418bb 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -59,6 +59,11 @@ dspace.name = DSpace at My University # Since DSpace 7, SOLR must be installed as a stand-alone service #solr.server = http://localhost:8983/solr +# Solr core name prefix. +# If you connect multiple instances of DSpace to a single Solr instance, you +# can organize them with a common core name prefix. +#solr.multicorePrefix = + ########################## # DATABASE CONFIGURATION # ########################## From 85a3dd6ff106d8dd1ffe6ab0997a23b300711c0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Feb 2022 20:46:43 +0000 Subject: [PATCH 0721/1254] Bump xercesImpl from 2.12.0 to 2.12.2 Bumps xercesImpl from 2.12.0 to 2.12.2. --- updated-dependencies: - dependency-name: xerces:xercesImpl 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 6dbc35d8ac..08a614c482 100644 --- a/pom.xml +++ b/pom.xml @@ -1570,7 +1570,7 @@ xerces xercesImpl - 2.12.0 + 2.12.2 xml-apis From 87d91c75bb73b7baf24ef453f014e5c60a3ece40 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Fri, 25 Feb 2022 15:44:29 +0100 Subject: [PATCH 0722/1254] Add ITs to verify all streams from retrieve() are closed --- .../app/rest/BitstreamRestControllerIT.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index f07aae876f..39931fc3ee 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -24,6 +24,11 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -73,10 +78,13 @@ import org.dspace.statistics.ObjectCount; import org.dspace.statistics.SolrLoggerServiceImpl; import org.dspace.statistics.factory.StatisticsServiceFactory; import org.dspace.statistics.service.SolrLoggerService; +import org.dspace.storage.bitstore.factory.StorageServiceFactory; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; /** * Integration test to test the /api/core/bitstreams/[id]/* endpoints @@ -968,4 +976,110 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest bitstreamService.getMetadataByMetadataString(bitstream, "dc.format") )); } + + + @Test + public void closeInputStreamsRegularDownload() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item with a bitstream + String bitstreamContent = "0123456789"; + + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test bitstream") + .withDescription("This is a bitstream to test range requests") + .withMimeType("text/plain") + .build(); + } + context.restoreAuthSystemState(); + + var bitstreamStorageService = StorageServiceFactory.getInstance().getBitstreamStorageService(); + var inputStream = bitstreamStorageService.retrieve(context, bitstream); + var inputStreamSpy = spy(inputStream); + var bitstreamStorageServiceSpy = spy(bitstreamStorageService); + ReflectionTestUtils.setField(bitstreamService, "bitstreamStorageService", bitstreamStorageServiceSpy); + doReturn(inputStreamSpy).when(bitstreamStorageServiceSpy).retrieve(any(), eq(bitstream)); + + //** WHEN ** + //We download the bitstream + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + //** THEN ** + .andExpect(status().isOk()); + + Mockito.verify(bitstreamStorageServiceSpy, times(1)).retrieve(any(), eq(bitstream)); + Mockito.verify(inputStreamSpy, times(1)).close(); + } + + @Test + public void closeInputStreamsDownloadWithCoverPage() throws Exception { + configurationService.setProperty("citation-page.enable_globally", true); + citationDocumentService.afterPropertiesSet(); + context.turnOffAuthorisationSystem(); + + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item with a bitstream + File originalPdf = new File(testProps.getProperty("test.bitstream")); + + try (InputStream is = new FileInputStream(originalPdf)) { + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item citation cover page test 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test bitstream") + .withDescription("This is a bitstream to test the citation cover page.") + .withMimeType("application/pdf") + .build(); + } + context.restoreAuthSystemState(); + + var bitstreamStorageService = StorageServiceFactory.getInstance().getBitstreamStorageService(); + var inputStreamSpy = spy(bitstreamStorageService.retrieve(context, bitstream)); + var inputStreamSpy2 = spy(bitstreamStorageService.retrieve(context, bitstream)); + var bitstreamStorageServiceSpy = spy(bitstreamStorageService); + ReflectionTestUtils.setField(bitstreamService, "bitstreamStorageService", bitstreamStorageServiceSpy); + doReturn(inputStreamSpy, inputStreamSpy2).when(bitstreamStorageServiceSpy).retrieve(any(), eq(bitstream)); + + //** WHEN ** + //We download the bitstream + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + //** THEN ** + .andExpect(status().isOk()); + + Mockito.verify(bitstreamStorageServiceSpy, times(2)).retrieve(any(), eq(bitstream)); + Mockito.verify(inputStreamSpy, times(1)).close(); + Mockito.verify(inputStreamSpy2, times(1)).close(); + } + } From 3f9506e62307e7ea876c0ee590a6bbd03c146501 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Mon, 28 Feb 2022 09:54:03 +0100 Subject: [PATCH 0723/1254] Prevent creating the citation page twice --- .../java/org/dspace/curate/CitationPage.java | 4 +- .../CitationDocumentServiceImpl.java | 5 +- .../service/CitationDocumentService.java | 3 +- .../app/rest/BitstreamRestController.java | 16 ++---- .../app/rest/utils/BitstreamResource.java | 49 ++++++++++++++----- .../app/rest/BitstreamRestControllerIT.java | 6 +-- 6 files changed, 47 insertions(+), 36 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CitationPage.java b/dspace-api/src/main/java/org/dspace/curate/CitationPage.java index dbdd070145..1b267286a4 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CitationPage.java +++ b/dspace-api/src/main/java/org/dspace/curate/CitationPage.java @@ -7,6 +7,7 @@ */ package org.dspace.curate; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; @@ -154,7 +155,8 @@ public class CitationPage extends AbstractCurationTask { try { //Create the cited document InputStream citedInputStream = - citationDocument.makeCitedDocument(Curator.curationContext(), bitstream).getLeft(); + new ByteArrayInputStream( + citationDocument.makeCitedDocument(Curator.curationContext(), bitstream).getLeft()); //Add the cited document to the approiate bundle this.addCitedPageToItem(citedInputStream, bundle, pBundle, dBundle, displayMap, item, bitstream); diff --git a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java index d51a3dfc7f..c20961db75 100644 --- a/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/disseminate/CitationDocumentServiceImpl.java @@ -8,7 +8,6 @@ package org.dspace.disseminate; import java.awt.Color; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -297,7 +296,7 @@ public class CitationDocumentServiceImpl implements CitationDocumentService, Ini } @Override - public Pair makeCitedDocument(Context context, Bitstream bitstream) + public Pair makeCitedDocument(Context context, Bitstream bitstream) throws IOException, SQLException, AuthorizeException { PDDocument document = new PDDocument(); PDDocument sourceDocument = new PDDocument(); @@ -318,7 +317,7 @@ public class CitationDocumentServiceImpl implements CitationDocumentService, Ini document.save(out); byte[] data = out.toByteArray(); - return Pair.of(new ByteArrayInputStream(data), Long.valueOf(data.length)); + return Pair.of(data, Long.valueOf(data.length)); } } finally { diff --git a/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java b/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java index 4a59de3f5f..0566fc525c 100644 --- a/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java +++ b/dspace-api/src/main/java/org/dspace/disseminate/service/CitationDocumentService.java @@ -8,7 +8,6 @@ package org.dspace.disseminate.service; import java.io.IOException; -import java.io.InputStream; import java.sql.SQLException; import org.apache.commons.lang3.tuple.Pair; @@ -84,7 +83,7 @@ public interface CitationDocumentService { * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public Pair makeCitedDocument(Context context, Bitstream bitstream) + public Pair makeCitedDocument(Context context, Bitstream bitstream) throws IOException, SQLException, AuthorizeException; /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 75d3c9886c..42f0663928 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -12,7 +12,6 @@ import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFI import static org.springframework.web.bind.annotation.RequestMethod.PUT; import java.io.IOException; -import java.io.InputStream; import java.sql.SQLException; import java.util.List; import java.util.UUID; @@ -22,7 +21,6 @@ import javax.ws.rs.core.Response; import org.apache.catalina.connector.ClientAbortException; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -133,19 +131,12 @@ public class BitstreamRestController { } try { - long filesize; - if (citationDocumentService.isCitationEnabledForBitstream(bit, context)) { - final Pair citedDocument = citationDocumentService.makeCitedDocument(context, bit); - filesize = citedDocument.getRight(); - citedDocument.getLeft().close(); - } else { - filesize = bit.getSizeBytes(); - } + long filesize = bit.getSizeBytes(); + var citationEnabledForBitstream = citationDocumentService.isCitationEnabledForBitstream(bit, context); HttpHeadersInitializer httpHeadersInitializer = new HttpHeadersInitializer() .withBufferSize(BUFFER_SIZE) .withFileName(name) - .withLength(filesize) .withChecksum(bit.getChecksum()) .withMimetype(mimetype) .with(request) @@ -162,10 +153,9 @@ public class BitstreamRestController { } - org.dspace.app.rest.utils.BitstreamResource bitstreamResource = new org.dspace.app.rest.utils.BitstreamResource( - bit, name, uuid, filesize, currentUser != null ? currentUser.getID() : null); + bit, name, uuid, currentUser != null ? currentUser.getID() : null, citationEnabledForBitstream); //We have all the data we need, close the connection to the database so that it doesn't stay open during //download/streaming diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java index 5b8c54629c..6de71fed59 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java @@ -7,11 +7,14 @@ */ package org.dspace.app.rest.utils; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; import java.util.UUID; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.tuple.Pair; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.factory.ContentServiceFactory; @@ -35,8 +38,10 @@ public class BitstreamResource extends AbstractResource { private Bitstream bitstream; private String name; private UUID uuid; - private long sizeBytes; private UUID currentUserUUID; + private boolean shouldGenerateCoverPage; + private byte[] file; + private Long fileSize; private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); @@ -44,12 +49,30 @@ public class BitstreamResource extends AbstractResource { new DSpace().getServiceManager() .getServicesByType(CitationDocumentService.class).get(0); - public BitstreamResource(Bitstream bitstream, String name, UUID uuid, long sizeBytes, UUID currentUserUUID) { + public BitstreamResource(Bitstream bitstream, String name, UUID uuid, UUID currentUserUUID, + boolean shouldGenerateCoverPage) { this.bitstream = bitstream; this.name = name; this.uuid = uuid; - this.sizeBytes = sizeBytes; this.currentUserUUID = currentUserUUID; + this.shouldGenerateCoverPage = shouldGenerateCoverPage; + } + + private Pair getFileData(Context context, Bitstream bitstream) throws SQLException, + AuthorizeException, IOException { + if (file == null || fileSize == null) { + if (shouldGenerateCoverPage) { + var citedDocument = citationDocumentService.makeCitedDocument(context, bitstream); + this.file = citedDocument.getLeft(); + this.fileSize = citedDocument.getRight(); + } else { + var inputStream = bitstreamService.retrieve(context, bitstream); + this.file = IOUtils.toByteArray(inputStream); + inputStream.close(); + this.fileSize = bitstream.getSizeBytes(); + } + } + return Pair.of(file, fileSize); } @Override @@ -63,15 +86,8 @@ public class BitstreamResource extends AbstractResource { try { EPerson currentUser = ePersonService.find(context, currentUserUUID); context.setCurrentUser(currentUser); - InputStream out; - - if (citationDocumentService.isCitationEnabledForBitstream(bitstream, context)) { - out = citationDocumentService.makeCitedDocument(context, bitstream).getLeft(); - } else { - out = bitstreamService.retrieve(context, bitstream); - } - - return out; + Bitstream bitstream = bitstreamService.find(context, uuid); + return new ByteArrayInputStream(getFileData(context, bitstream).getLeft()); } catch (SQLException | AuthorizeException e) { throw new IOException(e); } finally { @@ -90,6 +106,13 @@ public class BitstreamResource extends AbstractResource { @Override public long contentLength() throws IOException { - return sizeBytes; + try (Context context = new Context()) { + EPerson currentUser = ePersonService.find(context, currentUserUUID); + context.setCurrentUser(currentUser); + Bitstream bitstream = bitstreamService.find(context, uuid); + return getFileData(context, bitstream).getRight(); + } catch (SQLException | AuthorizeException e) { + throw new IOException(e); + } } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 39931fc3ee..fa02037f4a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -1066,10 +1066,9 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest var bitstreamStorageService = StorageServiceFactory.getInstance().getBitstreamStorageService(); var inputStreamSpy = spy(bitstreamStorageService.retrieve(context, bitstream)); - var inputStreamSpy2 = spy(bitstreamStorageService.retrieve(context, bitstream)); var bitstreamStorageServiceSpy = spy(bitstreamStorageService); ReflectionTestUtils.setField(bitstreamService, "bitstreamStorageService", bitstreamStorageServiceSpy); - doReturn(inputStreamSpy, inputStreamSpy2).when(bitstreamStorageServiceSpy).retrieve(any(), eq(bitstream)); + doReturn(inputStreamSpy).when(bitstreamStorageServiceSpy).retrieve(any(), eq(bitstream)); //** WHEN ** //We download the bitstream @@ -1077,9 +1076,8 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest //** THEN ** .andExpect(status().isOk()); - Mockito.verify(bitstreamStorageServiceSpy, times(2)).retrieve(any(), eq(bitstream)); + Mockito.verify(bitstreamStorageServiceSpy, times(1)).retrieve(any(), eq(bitstream)); Mockito.verify(inputStreamSpy, times(1)).close(); - Mockito.verify(inputStreamSpy2, times(1)).close(); } } From d0aab90ffc9ff43f604c992e737cff239e562f84 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Tue, 1 Mar 2022 02:30:41 +0100 Subject: [PATCH 0724/1254] 88049: Add latest_version_status column to relationship table --- .../java/org/dspace/content/Relationship.java | 33 +++ .../content/RelationshipServiceImpl.java | 20 +- .../content/service/RelationshipService.java | 31 ++- ...on_status_column_to_relationship_table.sql | 10 + ...on_status_column_to_relationship_table.sql | 10 + ...on_status_column_to_relationship_table.sql | 10 + ...RelationshipServiceImplVersioningTest.java | 208 ++++++++++++++++++ 7 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql create mode 100644 dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java diff --git a/dspace-api/src/main/java/org/dspace/content/Relationship.java b/dspace-api/src/main/java/org/dspace/content/Relationship.java index 81d13d6c10..c42061dea8 100644 --- a/dspace-api/src/main/java/org/dspace/content/Relationship.java +++ b/dspace-api/src/main/java/org/dspace/content/Relationship.java @@ -89,6 +89,15 @@ public class Relationship implements ReloadableEntity { @Column(name = "rightward_value") private String rightwardValue; + /** + * Whether the left and/or right side of a given relationship are the "latest". + * A side of a relationship is "latest" if the item on that side has either no other versions, + * or the item on that side is the most recent version that is relevant to the given relationship. + * This column affects what version of an item appears on search pages or the relationship listings of other items. + */ + @Column(name = "latest_version_status") + private LatestVersionStatus latestVersionStatus = LatestVersionStatus.BOTH; + /** * Protected constructor, create object using: * {@link org.dspace.content.service.RelationshipService#create(Context)} } @@ -216,6 +225,30 @@ public class Relationship implements ReloadableEntity { this.rightwardValue = rightwardValue; } + /** + * Getter for {@link #latestVersionStatus}. + * @return the latest version status of this relationship. + */ + public LatestVersionStatus getLatestVersionStatus() { + return latestVersionStatus; + } + + /** + * Setter for {@link #latestVersionStatus}. + * @param latestVersionStatus the new latest version status for this relationship. + */ + public void setLatestVersionStatus(LatestVersionStatus latestVersionStatus) { + this.latestVersionStatus = latestVersionStatus; + } + + public enum LatestVersionStatus { + // NOTE: SQL migration expects BOTH to be the first constant in this enum! + BOTH, // both items in this relationship are the "latest" + LEFT_ONLY, // the left-hand item of this relationship is the "latest", but the right-hand item is not + RIGHT_ONLY // the right-hand item of this relationship is the "latest", but the left-hand item is not + // NOTE: one side of any given relationship should ALWAYS be the "latest" + } + /** * Standard getter for the ID for this Relationship * @return The ID of this relationship diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index 1c99878e81..f4f1341088 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -20,6 +20,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Relationship.LatestVersionStatus; import org.dspace.content.dao.RelationshipDAO; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.ItemService; @@ -76,9 +77,10 @@ public class RelationshipServiceImpl implements RelationshipService { @Override - public Relationship create(Context c, Item leftItem, Item rightItem, RelationshipType relationshipType, - int leftPlace, int rightPlace, String leftwardValue, String rightwardValue) - throws AuthorizeException, SQLException { + public Relationship create( + Context c, Item leftItem, Item rightItem, RelationshipType relationshipType, int leftPlace, int rightPlace, + String leftwardValue, String rightwardValue, LatestVersionStatus latestVersionStatus + ) throws AuthorizeException, SQLException { Relationship relationship = new Relationship(); relationship.setLeftItem(leftItem); relationship.setRightItem(rightItem); @@ -87,9 +89,21 @@ public class RelationshipServiceImpl implements RelationshipService { relationship.setRightPlace(rightPlace); relationship.setLeftwardValue(leftwardValue); relationship.setRightwardValue(rightwardValue); + relationship.setLatestVersionStatus(latestVersionStatus); return create(c, relationship); } + @Override + public Relationship create( + Context c, Item leftItem, Item rightItem, RelationshipType relationshipType, int leftPlace, int rightPlace, + String leftwardValue, String rightwardValue + ) throws AuthorizeException, SQLException { + return create( + c, leftItem, rightItem, relationshipType, leftPlace, rightPlace, leftwardValue, rightwardValue, + LatestVersionStatus.BOTH + ); + } + @Override public Relationship create(Context context, Relationship relationship) throws SQLException, AuthorizeException { if (isRelationshipValidToCreate(context, relationship)) { diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index 2e0bb6f2be..d64425132f 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -14,6 +14,7 @@ import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.Relationship; +import org.dspace.content.Relationship.LatestVersionStatus; import org.dspace.content.RelationshipType; import org.dspace.core.Context; import org.dspace.service.DSpaceCRUDService; @@ -198,6 +199,27 @@ public interface RelationshipService extends DSpaceCRUDService { /** * This method is used to construct a Relationship object with all it's variables + * @param c The relevant DSpace context + * @param leftItem The leftItem Item object for the relationship + * @param rightItem The rightItem Item object for the relationship + * @param relationshipType The RelationshipType object for the relationship + * @param leftPlace The leftPlace integer for the relationship + * @param rightPlace The rightPlace integer for the relationship + * @param leftwardValue The leftwardValue string for the relationship + * @param rightwardValue The rightwardValue string for the relationship + * @param latestVersionStatus The latestVersionStatus value for the relationship + * @return The created Relationship object with the given properties + * @throws AuthorizeException If something goes wrong + * @throws SQLException If something goes wrong + */ + Relationship create( + Context c, Item leftItem, Item rightItem, RelationshipType relationshipType, int leftPlace, int rightPlace, + String leftwardValue, String rightwardValue, LatestVersionStatus latestVersionStatus + ) throws AuthorizeException, SQLException; + + /** + * This method is used to construct a Relationship object with all it's variables, + * except the latest version status * @param c The relevant DSpace context * @param leftItem The leftItem Item object for the relationship * @param rightItem The rightItem Item object for the relationship @@ -210,14 +232,15 @@ public interface RelationshipService extends DSpaceCRUDService { * @throws AuthorizeException If something goes wrong * @throws SQLException If something goes wrong */ - Relationship create(Context c, Item leftItem, Item rightItem, RelationshipType relationshipType, - int leftPlace, int rightPlace, String leftwardValue, String rightwardValue) - throws AuthorizeException, SQLException; + Relationship create( + Context c, Item leftItem, Item rightItem, RelationshipType relationshipType, int leftPlace, int rightPlace, + String leftwardValue, String rightwardValue + ) throws AuthorizeException, SQLException; /** * This method is used to construct a Relationship object with all it's variables, - * except the leftward and rightward labels + * except the leftward label, rightward label and latest version status * @param c The relevant DSpace context * @param leftItem The leftItem Item object for the relationship * @param rightItem The rightItem Item object for the relationship diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql new file mode 100644 index 0000000000..7bf3948d3a --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql @@ -0,0 +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/ +-- + +-- NOTE: default 0 ensures that existing relations have "latest_version_status" set to "both" (first constant in enum, see Relationship class) +ALTER TABLE relationship ADD COLUMN IF NOT EXISTS latest_version_status INTEGER DEFAULT 0 NOT NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql new file mode 100644 index 0000000000..3eb9ae6dd4 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql @@ -0,0 +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/ +-- + +-- NOTE: default 0 ensures that existing relations have "latest_version_status" set to "both" (first constant in enum, see Relationship class) +ALTER TABLE relationship ADD latest_version_status INTEGER DEFAULT 0 NOT NULL; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql new file mode 100644 index 0000000000..7bf3948d3a --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.02.28__add_last_version_status_column_to_relationship_table.sql @@ -0,0 +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/ +-- + +-- NOTE: default 0 ensures that existing relations have "latest_version_status" set to "both" (first constant in enum, see Relationship class) +ALTER TABLE relationship ADD COLUMN IF NOT EXISTS latest_version_status INTEGER DEFAULT 0 NOT NULL; diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java new file mode 100644 index 0000000000..a9e3bb04e7 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java @@ -0,0 +1,208 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.junit.Assert.assertEquals; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.RelationshipService; +import org.junit.Before; +import org.junit.Test; + +public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTestWithDatabase { + + private RelationshipService relationshipService; + + protected Community community; + protected Collection collection; + protected EntityType publicationEntityType; + protected EntityType personEntityType; + protected RelationshipType relationshipType; + protected Item publication1; + protected Item publication2; + protected Item publication3; + protected Item person1; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + + context.turnOffAuthorisationSystem(); + + community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + publicationEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication") + .build(); + + personEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person") + .build(); + + relationshipType = RelationshipTypeBuilder.createRelationshipTypeBuilder( + context, publicationEntityType, personEntityType, + "isAuthorOfPublication", "isPublicationOfAuthor", + null, null, null, null + ) + .withCopyToLeft(false) + .withCopyToRight(false) + .build(); + + publication1 = ItemBuilder.createItem(context, collection) + .withTitle("publication1") + .withMetadata("dspace", "entity", "type", publicationEntityType.getLabel()) + .build(); + + publication2 = ItemBuilder.createItem(context, collection) + .withTitle("publication2") + .withMetadata("dspace", "entity", "type", publicationEntityType.getLabel()) + .build(); + + publication3 = ItemBuilder.createItem(context, collection) + .withTitle("publication3") + .withMetadata("dspace", "entity", "type", publicationEntityType.getLabel()) + .build(); + + person1 = ItemBuilder.createItem(context, collection) + .withTitle("person1") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .build(); + + context.restoreAuthSystemState(); + } + + @Test + public void testRelationshipLatestVersionStatusDefault() throws Exception { + // create method #1 + context.turnOffAuthorisationSystem(); + Relationship relationship1 = relationshipService.create( + context, publication1, person1, relationshipType, 3, 5, "left", "right" + ); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship1.getLatestVersionStatus()); + Relationship relationship2 = relationshipService.find(context, relationship1.getID()); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship2.getLatestVersionStatus()); + + // create method #2 + context.turnOffAuthorisationSystem(); + Relationship relationship3 = relationshipService.create( + context, publication2, person1, relationshipType, 3, 5 + ); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship3.getLatestVersionStatus()); + Relationship relationship4 = relationshipService.find(context, relationship3.getID()); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship4.getLatestVersionStatus()); + + // create method #3 + Relationship inputRelationship = new Relationship(); + inputRelationship.setLeftItem(publication3); + inputRelationship.setRightItem(person1); + inputRelationship.setRelationshipType(relationshipType); + context.turnOffAuthorisationSystem(); + Relationship relationship5 = relationshipService.create(context, inputRelationship); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship5.getLatestVersionStatus()); + Relationship relationship6 = relationshipService.find(context, relationship5.getID()); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship6.getLatestVersionStatus()); + } + + @Test + public void testRelationshipLatestVersionStatusBoth() throws Exception { + // create method #1 + context.turnOffAuthorisationSystem(); + Relationship relationship1 = relationshipService.create( + context, publication1, person1, relationshipType, 3, 5, "left", "right", + Relationship.LatestVersionStatus.BOTH // set latest version status + ); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship1.getLatestVersionStatus()); + Relationship relationship2 = relationshipService.find(context, relationship1.getID()); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship2.getLatestVersionStatus()); + + // create method #2 + Relationship inputRelationship = new Relationship(); + inputRelationship.setLeftItem(publication2); + inputRelationship.setRightItem(person1); + inputRelationship.setRelationshipType(relationshipType); + inputRelationship.setLatestVersionStatus(Relationship.LatestVersionStatus.BOTH); // set latest version status + context.turnOffAuthorisationSystem(); + Relationship relationship3 = relationshipService.create(context, inputRelationship); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship3.getLatestVersionStatus()); + Relationship relationship4 = relationshipService.find(context, relationship3.getID()); + assertEquals(Relationship.LatestVersionStatus.BOTH, relationship4.getLatestVersionStatus()); + } + + @Test + public void testRelationshipLatestVersionStatusLeftOnly() throws Exception { + // create method #1 + context.turnOffAuthorisationSystem(); + Relationship relationship1 = relationshipService.create( + context, publication1, person1, relationshipType, 3, 5, "left", "right", + Relationship.LatestVersionStatus.LEFT_ONLY // set latest version status + ); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.LEFT_ONLY, relationship1.getLatestVersionStatus()); + Relationship relationship2 = relationshipService.find(context, relationship1.getID()); + assertEquals(Relationship.LatestVersionStatus.LEFT_ONLY, relationship2.getLatestVersionStatus()); + + // create method #2 + Relationship inputRelationship = new Relationship(); + inputRelationship.setLeftItem(publication2); + inputRelationship.setRightItem(person1); + inputRelationship.setRelationshipType(relationshipType); + inputRelationship.setLatestVersionStatus(Relationship.LatestVersionStatus.LEFT_ONLY); // set LVS + context.turnOffAuthorisationSystem(); + Relationship relationship3 = relationshipService.create(context, inputRelationship); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.LEFT_ONLY, relationship3.getLatestVersionStatus()); + Relationship relationship4 = relationshipService.find(context, relationship3.getID()); + assertEquals(Relationship.LatestVersionStatus.LEFT_ONLY, relationship4.getLatestVersionStatus()); + } + + @Test + public void testRelationshipLatestVersionStatusRightOnly() throws Exception { + // create method #1 + context.turnOffAuthorisationSystem(); + Relationship relationship1 = relationshipService.create( + context, publication1, person1, relationshipType, 3, 5, "left", "right", + Relationship.LatestVersionStatus.RIGHT_ONLY // set latest version status + ); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.RIGHT_ONLY, relationship1.getLatestVersionStatus()); + Relationship relationship2 = relationshipService.find(context, relationship1.getID()); + assertEquals(Relationship.LatestVersionStatus.RIGHT_ONLY, relationship2.getLatestVersionStatus()); + + // create method #2 + Relationship inputRelationship = new Relationship(); + inputRelationship.setLeftItem(publication2); + inputRelationship.setRightItem(person1); + inputRelationship.setRelationshipType(relationshipType); + inputRelationship.setLatestVersionStatus(Relationship.LatestVersionStatus.RIGHT_ONLY); // set LVS + context.turnOffAuthorisationSystem(); + Relationship relationship3 = relationshipService.create(context, inputRelationship); + context.restoreAuthSystemState(); + assertEquals(Relationship.LatestVersionStatus.RIGHT_ONLY, relationship3.getLatestVersionStatus()); + Relationship relationship4 = relationshipService.find(context, relationship3.getID()); + assertEquals(Relationship.LatestVersionStatus.RIGHT_ONLY, relationship4.getLatestVersionStatus()); + } + +} From a5f0c03a27e8b24d61e1f9c43173ac25bbf88575 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 2 Mar 2022 00:58:32 +0100 Subject: [PATCH 0725/1254] 88051: Support filtering non-latest relationships --- .../content/RelationshipServiceImpl.java | 68 +++-- .../dspace/content/dao/RelationshipDAO.java | 93 ++++--- .../content/dao/impl/RelationshipDAOImpl.java | 242 ++++++++++++------ .../content/service/RelationshipService.java | 82 ++++++ .../org/dspace/core/AbstractHibernateDAO.java | 19 +- 5 files changed, 368 insertions(+), 136 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index f4f1341088..52f5f966a2 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -293,14 +293,22 @@ public class RelationshipServiceImpl implements RelationshipService { } @Override - public List findByItem(Context context, Item item, Integer limit, Integer offset, - boolean excludeTilted) throws SQLException { + public List findByItem( + Context context, Item item, Integer limit, Integer offset, boolean excludeTilted + ) throws SQLException { + return findByItem(context, item, limit, offset, excludeTilted, true); + } - List list = relationshipDAO.findByItem(context, item, limit, offset, excludeTilted); + @Override + public List findByItem( + Context context, Item item, Integer limit, Integer offset, boolean excludeTilted, boolean excludeNonLinked + ) throws SQLException { + List list = + relationshipDAO.findByItem(context, item, limit, offset, excludeTilted, excludeNonLinked); list.sort((o1, o2) -> { int relationshipType = o1.getRelationshipType().getLeftwardType() - .compareTo(o2.getRelationshipType().getLeftwardType()); + .compareTo(o2.getRelationshipType().getLeftwardType()); if (relationshipType != 0) { return relationshipType; } else { @@ -652,22 +660,38 @@ public class RelationshipServiceImpl implements RelationshipService { public List findByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType) throws SQLException { - return relationshipDAO.findByItemAndRelationshipType(context, item, relationshipType, -1, -1); + return findByItemAndRelationshipType(context, item, relationshipType, -1, -1, true); } @Override public List findByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType, int limit, int offset) throws SQLException { - return relationshipDAO.findByItemAndRelationshipType(context, item, relationshipType, limit, offset); + return findByItemAndRelationshipType(context, item, relationshipType, limit, offset, true); } @Override - public List findByItemAndRelationshipType(Context context, Item item, - RelationshipType relationshipType, boolean isLeft, - int limit, int offset) - throws SQLException { - return relationshipDAO.findByItemAndRelationshipType(context, item, relationshipType, isLeft, limit, offset); + public List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, int limit, int offset, boolean excludeNonLatest + ) throws SQLException { + return relationshipDAO + .findByItemAndRelationshipType(context, item, relationshipType, limit, offset, excludeNonLatest); + } + + @Override + public List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, int limit, int offset + ) throws SQLException { + return findByItemAndRelationshipType(context, item, relationshipType, isLeft, limit, offset, true); + } + + @Override + public List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, int limit, int offset, + boolean excludeNonLatest + ) throws SQLException { + return relationshipDAO + .findByItemAndRelationshipType(context, item, relationshipType, isLeft, limit, offset, excludeNonLatest); } @Override @@ -704,7 +728,12 @@ public class RelationshipServiceImpl implements RelationshipService { @Override public int countByItem(Context context, Item item) throws SQLException { - return relationshipDAO.countByItem(context, item); + return countByItem(context, item, true); + } + + @Override + public int countByItem(Context context, Item item, boolean excludeNonLatest) throws SQLException { + return relationshipDAO.countByItem(context, item, excludeNonLatest); } @Override @@ -713,9 +742,18 @@ public class RelationshipServiceImpl implements RelationshipService { } @Override - public int countByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType, - boolean isLeft) throws SQLException { - return relationshipDAO.countByItemAndRelationshipType(context, item, relationshipType, isLeft); + public int countByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft + ) throws SQLException { + return countByItemAndRelationshipType(context, item, relationshipType, isLeft, true); + } + + @Override + public int countByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, boolean excludeNonLatest + ) throws SQLException { + return relationshipDAO + .countByItemAndRelationshipType(context, item, relationshipType, isLeft, excludeNonLatest); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java index 57b950a36b..c7a0a032ed 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java @@ -28,31 +28,38 @@ public interface RelationshipDAO extends GenericDAO { /** * This method returns a list of Relationship objects that have the given Item object * as a leftItem or a rightItem - * @param context The relevant DSpace context - * @param item The item that should be either a leftItem or a rightItem of all - * the Relationship objects in the returned list - * @param excludeTilted If true, excludes tilted relationships - * @return The list of Relationship objects that contain either a left or a - * right item that is equal to the given item - * @throws SQLException If something goes wrong + * @param context The relevant DSpace context + * @param item The item that should be either a leftItem or a rightItem of all + * the Relationship objects in the returned list + * @param excludeTilted If true, excludes tilted relationships + * @param excludeNonLatest If true, excludes all relationships for which the other item has a more recent version + * that is relevant for this relationship + * @return The list of Relationship objects that contain either a left or a + * right item that is equal to the given item + * @throws SQLException If something goes wrong */ - List findByItem(Context context, Item item, boolean excludeTilted) throws SQLException; + List findByItem( + Context context, Item item, boolean excludeTilted, boolean excludeNonLatest + ) throws SQLException; /** * This method returns a list of Relationship objects that have the given Item object * as a leftItem or a rightItem - * @param context The relevant DSpace context - * @param item The item that should be either a leftItem or a rightItem of all - * the Relationship objects in the returned list - * @param limit paging limit - * @param offset paging offset - * @param excludeTilted If true, excludes tilted relationships - * @return The list of Relationship objects that contain either a left or a - * right item that is equal to the given item - * @throws SQLException If something goes wrong + * @param context The relevant DSpace context + * @param item The item that should be either a leftItem or a rightItem of all + * the Relationship objects in the returned list + * @param limit paging limit + * @param offset paging offset + * @param excludeTilted If true, excludes tilted relationships + * @param excludeNonLatest If true, excludes all relationships for which the other item has a more recent version + * that is relevant for this relationship + * @return The list of Relationship objects that contain either a left or a + * right item that is equal to the given item + * @throws SQLException If something goes wrong */ - List findByItem(Context context, Item item, Integer limit, Integer offset, boolean excludeTilted) - throws SQLException; + List findByItem( + Context context, Item item, Integer limit, Integer offset, boolean excludeTilted, boolean excludeNonLatest + ) throws SQLException; /** * This method returns the next leftplace integer to use for a relationship with this item as the leftItem @@ -108,34 +115,41 @@ public interface RelationshipDAO extends GenericDAO { * It will construct a list of all Relationship objects that have the given RelationshipType object * as the relationshipType property * @param context The relevant DSpace context + * @param item item to filter by * @param relationshipType The RelationshipType object to be checked on * @param limit paging limit * @param offset paging offset - * @param item item to filter by + * @param excludeNonLatest If true, excludes all relationships for which the other item has a more recent version + * that is relevant for this relationship * @return A list of Relationship objects that have the given RelationshipType object as the * relationshipType property * @throws SQLException If something goes wrong */ - List findByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType, - Integer limit, Integer offset) throws SQLException; + List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, Integer limit, Integer offset, + boolean excludeNonLatest + ) throws SQLException; /** * This method returns a list of Relationship objects for the given RelationshipType object. * It will construct a list of all Relationship objects that have the given RelationshipType object * as the relationshipType property * @param context The relevant DSpace context + * @param item item to filter by * @param relationshipType The RelationshipType object to be checked on + * @param isLeft Is item left or right * @param limit paging limit * @param offset paging offset - * @param item item to filter by - * @param isLeft Is item left or right + * @param excludeNonLatest If true, excludes all relationships for which the other item has a more recent version + * that is relevant for this relationship * @return A list of Relationship objects that have the given RelationshipType object as the * relationshipType property * @throws SQLException If something goes wrong */ - List findByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType, - boolean isLeft, Integer limit, Integer offset) - throws SQLException; + List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, Integer limit, Integer offset, + boolean excludeNonLatest + ) throws SQLException; /** * This method returns a list of Relationship objects for the given typeName @@ -183,28 +197,33 @@ public interface RelationshipDAO extends GenericDAO { /** * This method returns a count of Relationship objects that have the given Item object * as a leftItem or a rightItem - * @param context The relevant DSpace context - * @param item The item that should be either a leftItem or a rightItem of all - * the Relationship objects in the returned list + * @param context The relevant DSpace context + * @param item The item that should be either a leftItem or a rightItem of all + * the Relationship objects in the returned list + * @param excludeNonLatest if true, exclude relationships for which the opposite item is not the latest version + * that is relevant * @return The list of Relationship objects that contain either a left or a * right item that is equal to the given item * @throws SQLException If something goes wrong */ - int countByItem(Context context, Item item) throws SQLException; + int countByItem(Context context, Item item, boolean excludeNonLatest) throws SQLException; /** * Count total number of relationships (rows in relationship table) by an item and a relationship type and a boolean * indicating whether the item should be the leftItem or the rightItem * - * @param context context - * @param relationshipType relationship type to filter by - * @param item item to filter by - * @param isLeft Indicating whether the counted Relationships should have the given Item on the left side or not + * @param context context + * @param relationshipType relationship type to filter by + * @param item item to filter by + * @param isLeft indicating whether the counted Relationships should have the given Item on the left side + * @param excludeNonLatest if true, exclude relationships for which the opposite item is not the latest version + * that is relevant * @return total count * @throws SQLException if database error */ - int countByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType, boolean isLeft) - throws SQLException; + int countByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, boolean excludeNonLatest + ) throws SQLException; /** * Count total number of relationships (rows in relationship table) given a typeName diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index 48baf45f23..acd5af1509 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -14,6 +14,7 @@ import java.util.UUID; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.dspace.content.Item; @@ -30,60 +31,147 @@ import org.dspace.core.Context; public class RelationshipDAOImpl extends AbstractHibernateDAO implements RelationshipDAO { @Override - public List findByItem(Context context, Item item, boolean excludeTilted) throws SQLException { - return findByItem(context, item, -1, -1, excludeTilted); + public List findByItem( + Context context, Item item, boolean excludeTilted, boolean excludeNonLatest + ) throws SQLException { + return findByItem(context, item, -1, -1, excludeTilted, excludeNonLatest); } @Override - public List findByItem(Context context, Item item, Integer limit, Integer offset, - boolean excludeTilted) throws SQLException { - + public List findByItem( + Context context, Item item, Integer limit, Integer offset, boolean excludeTilted, boolean excludeNonLatest + ) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); criteriaQuery.select(relationshipRoot); - if (excludeTilted) { - // If this item is the left item, - // return relationships for types which are not tilted right (tilted is either left nor null) - // If this item is the right item, - // return relationships for types which are not tilted left (tilted is either right nor null) - criteriaQuery - .where(criteriaBuilder.or( - criteriaBuilder.and( - criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item), - criteriaBuilder.or( - criteriaBuilder.isNull(relationshipRoot.get(Relationship_.relationshipType) - .get(RelationshipType_.tilted)), - criteriaBuilder.notEqual(relationshipRoot - .get(Relationship_.relationshipType) - .get(RelationshipType_.tilted), RelationshipType.Tilted.RIGHT))), - criteriaBuilder.and( - criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item), - criteriaBuilder.or( - criteriaBuilder.isNull(relationshipRoot.get(Relationship_.relationshipType) - .get(RelationshipType_.tilted)), - criteriaBuilder.notEqual(relationshipRoot - .get(Relationship_.relationshipType) - .get(RelationshipType_.tilted), RelationshipType.Tilted.LEFT))))); - } else { - criteriaQuery - .where(criteriaBuilder.or(criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item), - criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item))); - } + + criteriaQuery.where( + criteriaBuilder.or( + getLeftItemPredicate(criteriaBuilder, relationshipRoot, item, excludeTilted, excludeNonLatest), + getRightItemPredicate(criteriaBuilder, relationshipRoot, item, excludeTilted, excludeNonLatest) + ) + ); + return list(context, criteriaQuery, false, Relationship.class, limit, offset); } - @Override - public int countByItem(Context context, Item item) - throws SQLException { + /** + * Get the predicate for a criteria query that selects relationships by their left item. + * @param criteriaBuilder the criteria builder. + * @param relationshipRoot the relationship root. + * @param item the item that is being searched for. + * @param excludeTilted if true, exclude tilted relationships. + * @param excludeNonLatest if true, exclude relationships for which the opposite item is not the latest version + * that is relevant. + * @return a predicate that satisfies the given restrictions. + */ + protected Predicate getLeftItemPredicate( + CriteriaBuilder criteriaBuilder, Root relationshipRoot, Item item, + boolean excludeTilted, boolean excludeNonLatest + ) { + List predicates = new ArrayList<>(); + // match relationships based on the left item + predicates.add( + criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item) + ); + + if (excludeTilted) { + // if this item is the left item, + // return relationships for types which are NOT tilted right (tilted is either left nor null) + predicates.add( + criteriaBuilder.or( + criteriaBuilder.isNull( + relationshipRoot.get(Relationship_.relationshipType).get(RelationshipType_.tilted) + ), + criteriaBuilder.notEqual( + relationshipRoot.get(Relationship_.relationshipType).get(RelationshipType_.tilted), + RelationshipType.Tilted.RIGHT + ) + ) + ); + } + + if (excludeNonLatest) { + // if this item is the left item, + // return relationships for which the right item is the "latest" version that is relevant. + predicates.add( + criteriaBuilder.notEqual( + relationshipRoot.get(Relationship_.LATEST_VERSION_STATUS), + Relationship.LatestVersionStatus.LEFT_ONLY + ) + ); + } + + return criteriaBuilder.and(predicates.toArray(new Predicate[]{})); + } + + /** + * Get the predicate for a criteria query that selects relationships by their right item. + * @param criteriaBuilder the criteria builder. + * @param relationshipRoot the relationship root. + * @param item the item that is being searched for. + * @param excludeTilted if true, exclude tilted relationships. + * @param excludeNonLatest if true, exclude relationships for which the opposite item is not the latest version + * that is relevant. + * @return a predicate that satisfies the given restrictions. + */ + protected Predicate getRightItemPredicate( + CriteriaBuilder criteriaBuilder, Root relationshipRoot, Item item, + boolean excludeTilted, boolean excludeNonLatest + ) { + List predicates = new ArrayList<>(); + + // match relationships based on the right item + predicates.add( + criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item) + ); + + if (excludeTilted) { + // if this item is the right item, + // return relationships for types which are NOT tilted left (tilted is either right nor null) + predicates.add( + criteriaBuilder.or( + criteriaBuilder.isNull( + relationshipRoot.get(Relationship_.relationshipType).get(RelationshipType_.tilted) + ), + criteriaBuilder.notEqual( + relationshipRoot.get(Relationship_.relationshipType).get(RelationshipType_.tilted), + RelationshipType.Tilted.LEFT + ) + ) + ); + } + + if (excludeNonLatest) { + // if this item is the right item, + // return relationships for which the left item is the "latest" version that is relevant. + predicates.add( + criteriaBuilder.notEqual( + relationshipRoot.get(Relationship_.LATEST_VERSION_STATUS), + Relationship.LatestVersionStatus.RIGHT_ONLY + ) + ); + } + + return criteriaBuilder.and(predicates.toArray(new Predicate[]{})); + } + + @Override + public int countByItem(Context context, Item item, boolean excludeNonLatest) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); criteriaQuery.select(relationshipRoot); - criteriaQuery - .where(criteriaBuilder.or(criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item), - criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item))); + + criteriaQuery.where( + criteriaBuilder.or( + getLeftItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest), + getRightItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest) + ) + ); + return count(context, criteriaQuery, criteriaBuilder, relationshipRoot); } @@ -140,46 +228,50 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl } @Override - public List findByItemAndRelationshipType(Context context, Item item, - RelationshipType relationshipType, Integer limit, - Integer offset) - throws SQLException { - + public List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, Integer limit, Integer offset, + boolean excludeNonLatest + ) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); criteriaQuery.select(relationshipRoot); - criteriaQuery - .where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), - relationshipType), criteriaBuilder.or - (criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item), - criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item))); + + criteriaQuery.where( + criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), relationshipType), + criteriaBuilder.or( + getLeftItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest), + getRightItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest) + ) + ); + return list(context, criteriaQuery, true, Relationship.class, limit, offset); } @Override - public List findByItemAndRelationshipType(Context context, Item item, - RelationshipType relationshipType, boolean isLeft, - Integer limit, Integer offset) - throws SQLException { - + public List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, Integer limit, Integer offset, + boolean excludeNonLatest + ) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); criteriaQuery.select(relationshipRoot); + if (isLeft) { - criteriaQuery - .where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), - relationshipType), - criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item)); + criteriaQuery.where( + criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), relationshipType), + getLeftItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest) + ); criteriaQuery.orderBy(criteriaBuilder.asc(relationshipRoot.get(Relationship_.leftPlace))); } else { - criteriaQuery - .where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), - relationshipType), - criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item)); + criteriaQuery.where( + criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), relationshipType), + getRightItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest) + ); criteriaQuery.orderBy(criteriaBuilder.asc(relationshipRoot.get(Relationship_.rightPlace))); } + return list(context, criteriaQuery, true, Relationship.class, limit, offset); } @@ -228,24 +320,26 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl } @Override - public int countByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType, - boolean isLeft) throws SQLException { - + public int countByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, boolean excludeNonLatest + ) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); criteriaQuery.select(relationshipRoot); + if (isLeft) { - criteriaQuery - .where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), - relationshipType), - criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item)); + criteriaQuery.where( + criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), relationshipType), + getLeftItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest) + ); } else { - criteriaQuery - .where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), - relationshipType), - criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item)); + criteriaQuery.where( + criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), relationshipType), + getRightItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest) + ); } + return count(context, criteriaQuery, criteriaBuilder, relationshipRoot); } diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index d64425132f..f54e6f07f8 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -50,6 +50,25 @@ public interface RelationshipService extends DSpaceCRUDService { List findByItem(Context context, Item item, Integer limit, Integer offset, boolean excludeTilted) throws SQLException; + /** + * Retrieves the list of Relationships currently in the system for which the given Item is either + * a leftItem or a rightItem object + * @param context The relevant DSpace context + * @param item The Item that has to be the left or right item for the relationship to be + * included in the list + * @param limit paging limit + * @param offset paging offset + * @param excludeTilted If true, excludes tilted relationships + * @param excludeNonLatest If true, excludes all relationships for which the other item has a more recent version + * that is relevant for this relationship + * @return The list of relationships for which each relationship adheres to the above + * listed constraint + * @throws SQLException If something goes wrong + */ + List findByItem( + Context context, Item item, Integer limit, Integer offset, boolean excludeTilted, boolean excludeNonLatest + ) throws SQLException; + /** * Retrieves the full list of relationships currently in the system * @param context The relevant DSpace context @@ -129,6 +148,22 @@ public interface RelationshipService extends DSpaceCRUDService { RelationshipType relationshipType, int limit, int offset) throws SQLException; + /** + * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given + * Item object and for which the RelationshipType object is equal to the relationshipType property + * @param context The relevant DSpace context + * @param item The Item object to be matched on the leftItem or rightItem for the relationship + * @param relationshipType The RelationshipType object that will be used to check the Relationship on + * @param excludeNonLatest If true, excludes all relationships for which the other item has a more recent version + * that is relevant for this relationship + * @return The list of Relationship objects that have the given Item object as leftItem or rightItem and + * for which the relationshipType property is equal to the given RelationshipType + * @throws SQLException If something goes wrong + */ + public List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, int limit, int offset, boolean excludeNonLatest + ) throws SQLException; + /** * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given * Item object and for which the RelationshipType object is equal to the relationshipType property @@ -145,6 +180,24 @@ public interface RelationshipService extends DSpaceCRUDService { int limit, int offset) throws SQLException; + /** + * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given + * Item object and for which the RelationshipType object is equal to the relationshipType property + * @param context The relevant DSpace context + * @param item The Item object to be matched on the leftItem or rightItem for the relationship + * @param relationshipType The RelationshipType object that will be used to check the Relationship on + * @param isLeft Is the item left or right + * @param excludeNonLatest If true, excludes all relationships for which the other item has a more recent version + * that is relevant for this relationship + * @return The list of Relationship objects that have the given Item object as leftItem or rightItem and + * for which the relationshipType property is equal to the given RelationshipType + * @throws SQLException If something goes wrong + */ + public List findByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, int limit, int offset, + boolean excludeNonLatest + ) throws SQLException; + /** * This method will update the place for the Relationship and all other relationships found by the items and * relationship type of the given Relationship. It will give this Relationship the last place in both the @@ -310,6 +363,20 @@ public interface RelationshipService extends DSpaceCRUDService { */ int countByItem(Context context, Item item) throws SQLException; + /** + * This method returns a count of Relationship objects that have the given Item object + * as a leftItem or a rightItem + * @param context The relevant DSpace context + * @param item The item that should be either a leftItem or a rightItem of all + * the Relationship objects in the returned list + * @param excludeNonLatest if true, exclude relationships for which the opposite item is not the latest version + * that is relevant + * @return The list of Relationship objects that contain either a left or a + * right item that is equal to the given item + * @throws SQLException If something goes wrong + */ + int countByItem(Context context, Item item, boolean excludeNonLatest) throws SQLException; + /** * Count total number of relationships (rows in relationship table) by a relationship type and a boolean indicating * whether the relationship should contain the item on the left side or not @@ -323,6 +390,21 @@ public interface RelationshipService extends DSpaceCRUDService { int countByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType, boolean isLeft) throws SQLException; + /** + * Count total number of relationships (rows in relationship table) by a relationship type and a boolean indicating + * whether the relationship should contain the item on the left side or not + * @param context context + * @param relationshipType relationship type to filter by + * @param isLeft Indicating whether the counted Relationships should have the given Item on the left side + * @param excludeNonLatest If true, excludes all relationships for which the other item has a more recent version + * that is relevant for this relationship + * @return total count with the given parameters + * @throws SQLException if database error + */ + int countByItemAndRelationshipType( + Context context, Item item, RelationshipType relationshipType, boolean isLeft, boolean excludeNonLatest + ) throws SQLException; + /** * Count total number of relationships (rows in relationship table) * by a relationship leftward or rightward typeName diff --git a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java index 34a04056ce..32ad747d76 100644 --- a/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java +++ b/dspace-api/src/main/java/org/dspace/core/AbstractHibernateDAO.java @@ -155,12 +155,11 @@ public abstract class AbstractHibernateDAO implements GenericDAO { * @return A list of distinct results as depicted by the CriteriaQuery and parameters * @throws SQLException */ - public List list(Context context, CriteriaQuery criteriaQuery, boolean cacheable, Class clazz, int maxResults, - int offset) throws SQLException { + public List list( + Context context, CriteriaQuery criteriaQuery, boolean cacheable, Class clazz, int maxResults, int offset + ) throws SQLException { criteriaQuery.distinct(true); - @SuppressWarnings("unchecked") - List result = (List) executeCriteriaQuery(context, criteriaQuery, cacheable, maxResults, offset); - return result; + return executeCriteriaQuery(context, criteriaQuery, cacheable, maxResults, offset); } /** @@ -183,12 +182,12 @@ public abstract class AbstractHibernateDAO implements GenericDAO { * @return A list of results determined by the CriteriaQuery and parameters * @throws SQLException */ - public List list(Context context, CriteriaQuery criteriaQuery, boolean cacheable, Class clazz, int maxResults, - int offset, boolean distinct) throws SQLException { + public List list( + Context context, CriteriaQuery criteriaQuery, boolean cacheable, Class clazz, int maxResults, int offset, + boolean distinct + ) throws SQLException { criteriaQuery.distinct(distinct); - @SuppressWarnings("unchecked") - List result = (List) executeCriteriaQuery(context, criteriaQuery, cacheable, maxResults, offset); - return result; + return executeCriteriaQuery(context, criteriaQuery, cacheable, maxResults, offset); } /** From 85d05e6ea8b8e53e204089b728a6f68d999d71ab Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 2 Mar 2022 13:48:50 +0100 Subject: [PATCH 0726/1254] 88051: Fix tests --- .../java/org/dspace/content/RelationshipServiceImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java index 5d6197e494..fbcac3554f 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java @@ -113,7 +113,7 @@ public class RelationshipServiceImplTest { when(relationshipService.findByItem(context, cindy, -1, -1, false)).thenReturn(relationshipTest); // Mock the state of objects utilized in findByItem() to meet the success criteria of the invocation - when(relationshipDAO.findByItem(context, cindy, -1, -1, false)).thenReturn(relationshipTest); + when(relationshipDAO.findByItem(context, cindy, -1, -1, false, false)).thenReturn(relationshipTest); List results = relationshipService.findByItem(context, cindy); assertEquals("TestFindByItem 0", relationshipTest, results); From 0b31955f83c4e02ef74ecf7f42af4d8fddc16790 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 2 Mar 2022 16:44:59 +0100 Subject: [PATCH 0727/1254] 88051: Write tests for excludeNonLatest flag --- .../dspace/builder/RelationshipBuilder.java | 6 + ...RelationshipServiceImplVersioningTest.java | 848 ++++++++++++++++++ 2 files changed, 854 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java index 8746033419..3c9c6800df 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java @@ -139,4 +139,10 @@ public class RelationshipBuilder extends AbstractBuilder relationships) { + assertNotNull(relationships); + assertEquals(1, relationships.size()); + assertEquals(expectedRelationship, relationships.get(0)); + } + + protected void assertNoRelationship(List relationships) { + assertNotNull(relationships); + assertEquals(0, relationships.size()); + } + + @Test + public void testExcludeNonLatestBoth() throws Exception { + context.turnOffAuthorisationSystem(); + Relationship relationship1 = RelationshipBuilder + .createRelationshipBuilder(context, publication1, person1, relationshipType) + .withLatestVersionStatus(Relationship.LatestVersionStatus.BOTH) + .build(); + context.restoreAuthSystemState(); + + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, false, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, false, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, false, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, false, true) + ); + + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, -1, -1, false, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, -1, -1, false, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, -1, -1, false, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, -1, -1, false, true) + ); + + assertEquals(1, relationshipDAO.countByItem(context, publication1, false)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, false)); + assertEquals(1, relationshipDAO.countByItem(context, person1, true)); + + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, true) + ); + + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, true) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, true) + ); + + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, false, false) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, false, true) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, true, false) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, true, true) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, false, false) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, false, true) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, true, false) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, true, true) + ); + + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1) + ); + + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1, -1, -1, false) + ); + + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1, -1, -1, false, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1, -1, -1, false, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1, -1, -1, false, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1, -1, -1, false, true) + ); + + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType) + ); + + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1) + ); + + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, true) + ); + + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1) + ); + + assertNoRelationship( + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, false) + ); + assertNoRelationship( + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, true) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, false) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, true) + ); + + assertEquals(1, relationshipService.countByItem(context, publication1)); + assertEquals(1, relationshipService.countByItem(context, person1)); + + assertEquals(1, relationshipService.countByItem(context, publication1, false)); + assertEquals(1, relationshipService.countByItem(context, publication1, true)); + assertEquals(1, relationshipService.countByItem(context, person1, false)); + assertEquals(1, relationshipService.countByItem(context, person1, true)); + + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true) + ); + + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false, true) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true, false) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true, true) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false, false) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false, true) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true, true) + ); + } + + @Test + public void testExcludeNonLatestLeftOnly() throws Exception { + context.turnOffAuthorisationSystem(); + Relationship relationship1 = RelationshipBuilder + .createRelationshipBuilder(context, publication1, person1, relationshipType) + .withLatestVersionStatus(Relationship.LatestVersionStatus.LEFT_ONLY) + .build(); + context.restoreAuthSystemState(); + + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, false, false) + ); + assertNoRelationship( + relationshipDAO.findByItem(context, publication1, false, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, false, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, false, true) + ); + + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, -1, -1, false, false) + ); + assertNoRelationship( + relationshipDAO.findByItem(context, publication1, -1, -1, false, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, -1, -1, false, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, -1, -1, false, true) + ); + + assertEquals(1, relationshipDAO.countByItem(context, publication1, false)); + assertEquals(0, relationshipDAO.countByItem(context, publication1, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, false)); + assertEquals(1, relationshipDAO.countByItem(context, person1, true)); + + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, true) + ); + + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, true) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, true) + ); + + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, false, false) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, false, true) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, true, false) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, true, true) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, false, false) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, false, true) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, true, false) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, true, true) + ); + + assertNoRelationship( + relationshipService.findByItem(context, publication1) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1) + ); + + assertNoRelationship( + relationshipService.findByItem(context, publication1, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1, -1, -1, false) + ); + + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1, -1, -1, false, false) + ); + assertNoRelationship( + relationshipService.findByItem(context, publication1, -1, -1, false, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1, -1, -1, false, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1, -1, -1, false, true) + ); + + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType) + ); + + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1) + ); + + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, false) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, true) + ); + + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1) + ); + + assertNoRelationship( + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, false) + ); + assertNoRelationship( + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, false) + ); + assertNoRelationship( + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, true) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, false) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, true) + ); + + assertEquals(0, relationshipService.countByItem(context, publication1)); + assertEquals(1, relationshipService.countByItem(context, person1)); + + assertEquals(1, relationshipService.countByItem(context, publication1, false)); + assertEquals(0, relationshipService.countByItem(context, publication1, true)); + assertEquals(1, relationshipService.countByItem(context, person1, false)); + assertEquals(1, relationshipService.countByItem(context, person1, true)); + + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true) + ); + + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false, true) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true, true) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false, false) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false, true) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true, true) + ); + } + + @Test + public void testExcludeNonLatestRightOnly() throws Exception { + context.turnOffAuthorisationSystem(); + Relationship relationship1 = RelationshipBuilder + .createRelationshipBuilder(context, publication1, person1, relationshipType) + .withLatestVersionStatus(Relationship.LatestVersionStatus.RIGHT_ONLY) + .build(); + context.restoreAuthSystemState(); + + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, false, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, false, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, false, false) + ); + assertNoRelationship( + relationshipDAO.findByItem(context, person1, false, true) + ); + + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, -1, -1, false, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, publication1, -1, -1, false, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItem(context, person1, -1, -1, false, false) + ); + assertNoRelationship( + relationshipDAO.findByItem(context, person1, -1, -1, false, true) + ); + + assertEquals(1, relationshipDAO.countByItem(context, publication1, false)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, false)); + assertEquals(0, relationshipDAO.countByItem(context, person1, true)); + + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, true) + ); + + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, true) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, false) + ); + assertNoRelationship( + relationshipDAO.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, true) + ); + + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, false, false) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, false, true) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, true, false) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, publication1, relationshipType, true, true) + ); + assertEquals( + 1, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, false, false) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, false, true) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, true, false) + ); + assertEquals( + 0, relationshipDAO.countByItemAndRelationshipType(context, person1, relationshipType, true, true) + ); + + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1) + ); + assertNoRelationship( + relationshipService.findByItem(context, person1) + ); + + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1, -1, -1, false) + ); + assertNoRelationship( + relationshipService.findByItem(context, person1, -1, -1, false) + ); + + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1, -1, -1, false, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, publication1, -1, -1, false, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItem(context, person1, -1, -1, false, false) + ); + assertNoRelationship( + relationshipService.findByItem(context, person1, -1, -1, false, true) + ); + + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType) + ); + + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1) + ); + + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, false) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, -1, -1, true) + ); + + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1) + ); + + assertNoRelationship( + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, false) + ); + assertNoRelationship( + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, false, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, false) + ); + assertRelationship( + relationship1, + relationshipService + .findByItemAndRelationshipType(context, publication1, relationshipType, true, -1, -1, true) + ); + assertRelationship( + relationship1, + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, false) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, false, -1, -1, true) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, false) + ); + assertNoRelationship( + relationshipService.findByItemAndRelationshipType(context, person1, relationshipType, true, -1, -1, true) + ); + + assertEquals(1, relationshipService.countByItem(context, publication1)); + assertEquals(0, relationshipService.countByItem(context, person1)); + + assertEquals(1, relationshipService.countByItem(context, publication1, false)); + assertEquals(1, relationshipService.countByItem(context, publication1, true)); + assertEquals(1, relationshipService.countByItem(context, person1, false)); + assertEquals(0, relationshipService.countByItem(context, person1, true)); + + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true) + ); + + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false, true) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true, false) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, true, true) + ); + assertEquals( + 1, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, false, true) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true, false) + ); + assertEquals( + 0, relationshipService.countByItemAndRelationshipType(context, person1, relationshipType, true, true) + ); + } + } From 18e4fed9edf8f27fe2dc8051bef754623b2cbbe9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 17 Feb 2022 14:51:00 -0600 Subject: [PATCH 0728/1254] Enable default value for solr.authority.server --- dspace/config/dspace.cfg | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index a7b957bef6..31b4f8a6ce 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1413,9 +1413,12 @@ plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ ## See manual or org.dspace.content.authority.Choices source for descriptions. authority.minconfidence = ambiguous +# Solr Authority index location +# Default is ${solr.server}/authority, unless solr.multicorePrefix is specified +solr.authority.server=${solr.server}/${solr.multicorePrefix}authority + # Configuration settings for ORCID based authority control. -# Uncomment the lines below to enable configuration. -#solr.authority.server=${solr.server}/${solr.multicorePrefix}authority +# Uncomment the lines below to enable configuration #choices.plugin.dc.contributor.author = SolrAuthorAuthority #choices.presentation.dc.contributor.author = authorLookup #authority.controlled.dc.contributor.author = true From fb4eb05e5dc9d2fa13761ab7932ad791723161ea Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Thu, 3 Mar 2022 10:29:32 +0100 Subject: [PATCH 0729/1254] 88051: Fix tests --- ...RelationshipServiceImplVersioningTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java index 5ad52a3417..c4f718e836 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java @@ -131,6 +131,13 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe assertEquals(Relationship.LatestVersionStatus.BOTH, relationship5.getLatestVersionStatus()); Relationship relationship6 = relationshipService.find(context, relationship5.getID()); assertEquals(Relationship.LatestVersionStatus.BOTH, relationship6.getLatestVersionStatus()); + + // clean up + context.turnOffAuthorisationSystem(); + relationshipService.delete(context, relationship1); + relationshipService.delete(context, relationship3); + relationshipService.delete(context, relationship5); + context.restoreAuthSystemState(); } @Test @@ -158,6 +165,12 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe assertEquals(Relationship.LatestVersionStatus.BOTH, relationship3.getLatestVersionStatus()); Relationship relationship4 = relationshipService.find(context, relationship3.getID()); assertEquals(Relationship.LatestVersionStatus.BOTH, relationship4.getLatestVersionStatus()); + + // clean up + context.turnOffAuthorisationSystem(); + relationshipService.delete(context, relationship1); + relationshipService.delete(context, relationship3); + context.restoreAuthSystemState(); } @Test @@ -185,6 +198,12 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe assertEquals(Relationship.LatestVersionStatus.LEFT_ONLY, relationship3.getLatestVersionStatus()); Relationship relationship4 = relationshipService.find(context, relationship3.getID()); assertEquals(Relationship.LatestVersionStatus.LEFT_ONLY, relationship4.getLatestVersionStatus()); + + // clean up + context.turnOffAuthorisationSystem(); + relationshipService.delete(context, relationship1); + relationshipService.delete(context, relationship3); + context.restoreAuthSystemState(); } @Test @@ -212,6 +231,12 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe assertEquals(Relationship.LatestVersionStatus.RIGHT_ONLY, relationship3.getLatestVersionStatus()); Relationship relationship4 = relationshipService.find(context, relationship3.getID()); assertEquals(Relationship.LatestVersionStatus.RIGHT_ONLY, relationship4.getLatestVersionStatus()); + + // clean up + context.turnOffAuthorisationSystem(); + relationshipService.delete(context, relationship1); + relationshipService.delete(context, relationship3); + context.restoreAuthSystemState(); } protected void assertRelationship(Relationship expectedRelationship, List relationships) { From e783576476f57e9eae7ad2f34df7b3e16ea672d2 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Thu, 3 Mar 2022 14:35:04 +0100 Subject: [PATCH 0730/1254] 88051: Fix unnecessary stubbing exception --- .../java/org/dspace/content/RelationshipServiceImplTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java index fbcac3554f..4fb47376fb 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java @@ -112,9 +112,6 @@ public class RelationshipServiceImplTest { relationshipTest.add(getRelationship(bob, cindy, hasMother,1,0)); when(relationshipService.findByItem(context, cindy, -1, -1, false)).thenReturn(relationshipTest); - // Mock the state of objects utilized in findByItem() to meet the success criteria of the invocation - when(relationshipDAO.findByItem(context, cindy, -1, -1, false, false)).thenReturn(relationshipTest); - List results = relationshipService.findByItem(context, cindy); assertEquals("TestFindByItem 0", relationshipTest, results); for (int i = 0; i < relationshipTest.size(); i++) { From b912769580c732980af456f3170e79fce0906c48 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Fri, 4 Mar 2022 16:10:42 +0100 Subject: [PATCH 0731/1254] 88056: Bugfix: do not copy virtual metadata to new version of items --- .../org/dspace/versioning/AbstractVersionProvider.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 8b0ca9aeb8..6c04657ab4 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java @@ -12,6 +12,7 @@ import java.sql.SQLException; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; @@ -24,6 +25,7 @@ import org.dspace.content.MetadataValue; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.storage.bitstore.service.BitstreamStorageService; import org.springframework.beans.factory.annotation.Autowired; @@ -55,8 +57,9 @@ public abstract class AbstractVersionProvider { MetadataSchema metadataSchema = metadataField.getMetadataSchema(); String unqualifiedMetadataField = metadataSchema.getName() + "." + metadataField.getElement(); if (getIgnoredMetadataFields().contains(metadataField.toString('.')) || - getIgnoredMetadataFields().contains(unqualifiedMetadataField + "." + Item.ANY)) { - //Skip this metadata field + getIgnoredMetadataFields().contains(unqualifiedMetadataField + "." + Item.ANY) || + StringUtils.startsWith(aMd.getAuthority(), Constants.VIRTUAL_AUTHORITY_PREFIX)) { + //Skip this metadata field (ignored and/or virtual) continue; } From 272fd355353e2d75ba95ea3427b89450d053f383 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Fri, 4 Mar 2022 16:13:49 +0100 Subject: [PATCH 0732/1254] 88056: Copy relationships when creating new version of item --- .../DefaultItemVersionProvider.java | 57 ++ .../versioning/ItemVersionProvider.java | 7 + .../VersioningWithRelationshipsTest.java | 490 ++++++++++++++++++ 3 files changed, 554 insertions(+) create mode 100644 dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java diff --git a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java index 7903a49c31..d4590ae24e 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java @@ -15,7 +15,9 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.content.Item; +import org.dspace.content.Relationship; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.RelationshipService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; import org.dspace.identifier.IdentifierException; @@ -44,6 +46,8 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen protected VersioningService versioningService; @Autowired(required = true) protected IdentifierService identifierService; + @Autowired(required = true) + protected RelationshipService relationshipService; @Override public Item createNewItemAndAddItInWorkspace(Context context, Item nativeItem) { @@ -89,10 +93,18 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen } } + /** + * Copy all data (minus a few exceptions) from the old item to the new item. + * @param c the DSpace context. + * @param itemNew the new version of the item. + * @param previousItem the old version of the item. + * @return the new version of the item, with data from the old item. + */ @Override public Item updateItemState(Context c, Item itemNew, Item previousItem) { try { copyMetadata(c, itemNew, previousItem); + copyRelationships(c, itemNew, previousItem); createBundlesAndAddBitstreams(c, itemNew, previousItem); try { identifierService.reserve(c, itemNew); @@ -114,4 +126,49 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen throw new RuntimeException(e.getMessage(), e); } } + + /** + * Copy all relationships of the old item to the new item. + * At this point in the lifecycle of the item-version (before archival), only the opposite item receives + * "latest" status. On item archival of the item-version, the "latest" status of the relevant relationships + * will be updated. + * @param context the DSpace context. + * @param newItem the new version of the item. + * @param oldItem the old version of the item. + */ + protected void copyRelationships( + Context context, Item newItem, Item oldItem + ) throws SQLException, AuthorizeException { + List oldRelationships = relationshipService.findByItem(context, oldItem, -1, -1, false, true); + for (Relationship oldRelationship : oldRelationships) { + if (oldRelationship.getLeftItem().equals(oldItem)) { + // current item is on left side of this relationship + relationshipService.create( + context, + newItem, // new item + oldRelationship.getRightItem(), + oldRelationship.getRelationshipType(), + oldRelationship.getLeftPlace(), + oldRelationship.getRightPlace(), + oldRelationship.getLeftwardValue(), + oldRelationship.getRightwardValue(), + Relationship.LatestVersionStatus.RIGHT_ONLY // only mark the opposite side as "latest" for now + ); + } else if (oldRelationship.getRightItem().equals(oldItem)) { + // current item is on right side of this relationship + relationshipService.create( + context, + oldRelationship.getLeftItem(), + newItem, // new item + oldRelationship.getRelationshipType(), + oldRelationship.getLeftPlace(), + oldRelationship.getRightPlace(), + oldRelationship.getLeftwardValue(), + oldRelationship.getRightwardValue(), + Relationship.LatestVersionStatus.LEFT_ONLY // only mark the opposite side as "latest" for now + ); + } + } + } + } diff --git a/dspace-api/src/main/java/org/dspace/versioning/ItemVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/ItemVersionProvider.java index 83369e0465..74014b6262 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/ItemVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/ItemVersionProvider.java @@ -22,5 +22,12 @@ public interface ItemVersionProvider { public void deleteVersionedItem(Context c, Version versionToDelete, VersionHistory history) throws SQLException; + /** + * Copy all data (minus a few exceptions) from the old item to the new item. + * @param c the DSpace context. + * @param itemNew the new version of the item. + * @param previousItem the old version of the item. + * @return the new version of the item, with data from the old item. + */ public Item updateItemState(Context c, Item itemNew, Item previousItem); } diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java new file mode 100644 index 0000000000..449749aa99 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -0,0 +1,490 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.dspace.content.Relationship.LatestVersionStatus; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertNotSame; + +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.RelationshipBuilder; +import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.RelationshipService; +import org.dspace.versioning.Version; +import org.dspace.versioning.factory.VersionServiceFactory; +import org.dspace.versioning.service.VersioningService; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; + +public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWithDatabase { + + private RelationshipService relationshipService; + private VersioningService versioningService; + + protected Community community; + protected Collection collection; + protected EntityType publicationEntityType; + protected EntityType personEntityType; + protected EntityType projectEntityType; + protected EntityType orgUnitEntityType; + protected RelationshipType isAuthorOfPublication; + protected RelationshipType isProjectOfPublication; + protected RelationshipType isOrgUnitOfPublication; + protected RelationshipType isMemberOfProject; + protected RelationshipType isMemberOfOrgUnit; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + versioningService = VersionServiceFactory.getInstance().getVersionService(); + + context.turnOffAuthorisationSystem(); + + community = CommunityBuilder.createCommunity(context) + .withName("community") + .build(); + + collection = CollectionBuilder.createCollection(context, community) + .withName("collection") + .build(); + + publicationEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication") + .build(); + + personEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person") + .build(); + + projectEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Project") + .build(); + + orgUnitEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "OrgUnit") + .build(); + + isAuthorOfPublication = RelationshipTypeBuilder.createRelationshipTypeBuilder( + context, publicationEntityType, personEntityType, + "isAuthorOfPublication", "isPublicationOfAuthor", + null, null, null, null + ) + .withCopyToLeft(false) + .withCopyToRight(false) + .build(); + + isProjectOfPublication = RelationshipTypeBuilder.createRelationshipTypeBuilder( + context, publicationEntityType, projectEntityType, + "isProjectOfPublication", "isPublicationOfProject", + 0, null, 0, null + ) + .withCopyToLeft(false) + .withCopyToRight(false) + .build(); + + isOrgUnitOfPublication = RelationshipTypeBuilder.createRelationshipTypeBuilder( + context, publicationEntityType, orgUnitEntityType, + "isOrgUnitOfPublication", "isPublicationOfOrgUnit", + 0, null, 0, null + ) + .withCopyToLeft(false) + .withCopyToRight(false) + .build(); + + isMemberOfProject = RelationshipTypeBuilder.createRelationshipTypeBuilder( + context, projectEntityType, personEntityType, + "isMemberOfProject", "isProjectOfMember", + null, null, null, null + ) + .withCopyToLeft(false) + .withCopyToRight(false) + .build(); + + isMemberOfOrgUnit = RelationshipTypeBuilder.createRelationshipTypeBuilder( + context, orgUnitEntityType, personEntityType, + "isMemberOfOrgUnit", "isOrgUnitOfMember", + null, null, null, null + ) + .withCopyToLeft(false) + .withCopyToRight(false) + .build(); + } + + protected Matcher isRelationship( + Item leftItem, RelationshipType relationshipType, Item rightItem, LatestVersionStatus latestVersionStatus + ) { + return allOf( + hasProperty("leftItem", is(leftItem)), + hasProperty("relationshipType", is(relationshipType)), + hasProperty("rightItem", is(rightItem)), + // NOTE: place is not checked + hasProperty("leftwardValue", nullValue()), + hasProperty("rightwardValue", nullValue()), + hasProperty("latestVersionStatus", is(latestVersionStatus)) + ); + } + + @Test + public void test_createNewVersionOfItemOnLeftSideOfRelationships() throws Exception { + /////////////////////////////////////////////// + // create a publication with 3 relationships // + /////////////////////////////////////////////// + + Item person1 = ItemBuilder.createItem(context, collection) + .withTitle("person 1") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .build(); + + Item project1 = ItemBuilder.createItem(context, collection) + .withTitle("project 1") + .withMetadata("dspace", "entity", "type", projectEntityType.getLabel()) + .build(); + + Item orgUnit1 = ItemBuilder.createItem(context, collection) + .withTitle("org unit 1") + .withMetadata("dspace", "entity", "type", orgUnitEntityType.getLabel()) + .build(); + + Item originalPublication = ItemBuilder.createItem(context, collection) + .withTitle("original publication") + .withMetadata("dspace", "entity", "type", publicationEntityType.getLabel()) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, person1, isAuthorOfPublication) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, project1, isProjectOfPublication) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, orgUnit1, isOrgUnitOfPublication) + .build(); + + ///////////////////////////////////////////////////////// + // verify that the relationships were properly created // + ///////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + ///////////////////////////////////////////// + // create a new version of the publication // + ///////////////////////////////////////////// + + Version newVersion = versioningService.createNewVersion(context, originalPublication); + Item newPublication = newVersion.getItem(); + assertNotSame(originalPublication, newPublication); + + /////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = true) // + /////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + //////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = false) // + //////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + ////////////// + // clean up // + ////////////// + + versioningService.removeVersion(context, newPublication); + } + + @Test + public void test_createNewVersionOfItemOnRightSideOfRelationships() throws Exception { + /////////////////////////////////////////////// + // create a publication with 3 relationships // + /////////////////////////////////////////////// + + Item publication1 = ItemBuilder.createItem(context, collection) + .withTitle("publication 1") + .withMetadata("dspace", "entity", "type", publicationEntityType.getLabel()) + .build(); + + Item project1 = ItemBuilder.createItem(context, collection) + .withTitle("project 1") + .withMetadata("dspace", "entity", "type", projectEntityType.getLabel()) + .build(); + + Item orgUnit1 = ItemBuilder.createItem(context, collection) + .withTitle("org unit 1") + .withMetadata("dspace", "entity", "type", orgUnitEntityType.getLabel()) + .build(); + + Item originalPerson = ItemBuilder.createItem(context, collection) + .withTitle("original person") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, originalPerson, isAuthorOfPublication) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, project1, originalPerson, isMemberOfProject) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, orgUnit1, originalPerson, isMemberOfOrgUnit) + .build(); + + ///////////////////////////////////////////////////////// + // verify that the relationships were properly created // + ///////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPerson, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH), + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH), + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, publication1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + //////////////////////////////////////// + // create a new version of the person // + //////////////////////////////////////// + + Version newVersion = versioningService.createNewVersion(context, originalPerson); + Item newPerson = newVersion.getItem(); + assertNotSame(originalPerson, newPerson); + + /////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = true) // + /////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPerson, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH), + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH), + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, publication1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPerson, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY) + )) + ); + + //////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = false) // + //////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPerson, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH), + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH), + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, publication1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH), + isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH), + isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH), + isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPerson, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY) + )) + ); + + ////////////// + // clean up // + ////////////// + + versioningService.removeVersion(context, newPerson); + } + +} From 85b27525659e6b245a1ab3693e3a1aa5fda0d203 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Fri, 4 Mar 2022 17:31:04 +0100 Subject: [PATCH 0733/1254] 88146: Bugfix: Add excludeTilted to RelationshipDAO#countByItem, add note to docs --- .../content/RelationshipServiceImpl.java | 12 ++-- .../dspace/content/dao/RelationshipDAO.java | 3 +- .../content/dao/impl/RelationshipDAOImpl.java | 8 ++- .../content/service/RelationshipService.java | 15 +++- ...RelationshipServiceImplVersioningTest.java | 72 ++++++++++++------- .../ItemRelationshipLinkRepository.java | 4 +- 6 files changed, 76 insertions(+), 38 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index 52f5f966a2..87389a839f 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -301,10 +301,10 @@ public class RelationshipServiceImpl implements RelationshipService { @Override public List findByItem( - Context context, Item item, Integer limit, Integer offset, boolean excludeTilted, boolean excludeNonLinked + Context context, Item item, Integer limit, Integer offset, boolean excludeTilted, boolean excludeNonLatest ) throws SQLException { List list = - relationshipDAO.findByItem(context, item, limit, offset, excludeTilted, excludeNonLinked); + relationshipDAO.findByItem(context, item, limit, offset, excludeTilted, excludeNonLatest); list.sort((o1, o2) -> { int relationshipType = o1.getRelationshipType().getLeftwardType() @@ -728,12 +728,14 @@ public class RelationshipServiceImpl implements RelationshipService { @Override public int countByItem(Context context, Item item) throws SQLException { - return countByItem(context, item, true); + return countByItem(context, item, false, true); } @Override - public int countByItem(Context context, Item item, boolean excludeNonLatest) throws SQLException { - return relationshipDAO.countByItem(context, item, excludeNonLatest); + public int countByItem( + Context context, Item item, boolean excludeTilted, boolean excludeNonLatest + ) throws SQLException { + return relationshipDAO.countByItem(context, item, excludeTilted, excludeNonLatest); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java index c7a0a032ed..07edf59350 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java @@ -200,13 +200,14 @@ public interface RelationshipDAO extends GenericDAO { * @param context The relevant DSpace context * @param item The item that should be either a leftItem or a rightItem of all * the Relationship objects in the returned list + * @param excludeTilted if true, excludes tilted relationships * @param excludeNonLatest if true, exclude relationships for which the opposite item is not the latest version * that is relevant * @return The list of Relationship objects that contain either a left or a * right item that is equal to the given item * @throws SQLException If something goes wrong */ - int countByItem(Context context, Item item, boolean excludeNonLatest) throws SQLException; + int countByItem(Context context, Item item, boolean excludeTilted, boolean excludeNonLatest) throws SQLException; /** * Count total number of relationships (rows in relationship table) by an item and a relationship type and a boolean diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java index acd5af1509..182d229033 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -159,7 +159,9 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl } @Override - public int countByItem(Context context, Item item, boolean excludeNonLatest) throws SQLException { + public int countByItem( + Context context, Item item, boolean excludeTilted, boolean excludeNonLatest + ) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); @@ -167,8 +169,8 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl criteriaQuery.where( criteriaBuilder.or( - getLeftItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest), - getRightItemPredicate(criteriaBuilder, relationshipRoot, item, false, excludeNonLatest) + getLeftItemPredicate(criteriaBuilder, relationshipRoot, item, excludeTilted, excludeNonLatest), + getRightItemPredicate(criteriaBuilder, relationshipRoot, item, excludeTilted, excludeNonLatest) ) ); diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java index f54e6f07f8..e58bcca85c 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -123,6 +123,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given * Item object and for which the RelationshipType object is equal to the relationshipType property + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context The relevant DSpace context * @param item The Item object to be matched on the leftItem or rightItem for the relationship * @param relationshipType The RelationshipType object that will be used to check the Relationship on @@ -137,6 +138,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given * Item object and for which the RelationshipType object is equal to the relationshipType property + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context The relevant DSpace context * @param item The Item object to be matched on the leftItem or rightItem for the relationship * @param relationshipType The RelationshipType object that will be used to check the Relationship on @@ -151,6 +153,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given * Item object and for which the RelationshipType object is equal to the relationshipType property + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context The relevant DSpace context * @param item The Item object to be matched on the leftItem or rightItem for the relationship * @param relationshipType The RelationshipType object that will be used to check the Relationship on @@ -167,6 +170,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given * Item object and for which the RelationshipType object is equal to the relationshipType property + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context The relevant DSpace context * @param item The Item object to be matched on the leftItem or rightItem for the relationship * @param relationshipType The RelationshipType object that will be used to check the Relationship on @@ -183,6 +187,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given * Item object and for which the RelationshipType object is equal to the relationshipType property + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context The relevant DSpace context * @param item The Item object to be matched on the leftItem or rightItem for the relationship * @param relationshipType The RelationshipType object that will be used to check the Relationship on @@ -228,6 +233,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * This method returns a list of Relationship objects for which the relationshipType property is equal to the given * RelationshipType object + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context The relevant DSpace context * @param relationshipType The RelationshipType object that will be used to check the Relationship on * @return The list of Relationship objects for which the given RelationshipType object is equal @@ -239,6 +245,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * This method returns a list of Relationship objets for which the relationshipType property is equal to the given * RelationshipType object + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context The relevant DSpace context * @param relationshipType The RelationshipType object that will be used to check the Relationship on * @param limit paging limit @@ -343,7 +350,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * Count total number of relationships (rows in relationship table) by a relationship type - * + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context context * @param relationshipType relationship type to filter by * @return total count @@ -369,18 +376,19 @@ public interface RelationshipService extends DSpaceCRUDService { * @param context The relevant DSpace context * @param item The item that should be either a leftItem or a rightItem of all * the Relationship objects in the returned list + * @param excludeTilted if true, excludes tilted relationships * @param excludeNonLatest if true, exclude relationships for which the opposite item is not the latest version * that is relevant * @return The list of Relationship objects that contain either a left or a * right item that is equal to the given item * @throws SQLException If something goes wrong */ - int countByItem(Context context, Item item, boolean excludeNonLatest) throws SQLException; + int countByItem(Context context, Item item, boolean excludeTilted, boolean excludeNonLatest) throws SQLException; /** * Count total number of relationships (rows in relationship table) by a relationship type and a boolean indicating * whether the relationship should contain the item on the left side or not - * + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context context * @param relationshipType relationship type to filter by * @param isLeft Indicating whether the counted Relationships should have the given Item on the left side or not @@ -393,6 +401,7 @@ public interface RelationshipService extends DSpaceCRUDService { /** * Count total number of relationships (rows in relationship table) by a relationship type and a boolean indicating * whether the relationship should contain the item on the left side or not + * NOTE: tilted relationships are NEVER excluded when fetching one relationship type * @param context context * @param relationshipType relationship type to filter by * @param isLeft Indicating whether the counted Relationships should have the given Item on the left side diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java index c4f718e836..d42213da2c 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplVersioningTest.java @@ -293,10 +293,14 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe relationshipDAO.findByItem(context, person1, -1, -1, false, true) ); - assertEquals(1, relationshipDAO.countByItem(context, publication1, false)); - assertEquals(1, relationshipDAO.countByItem(context, publication1, true)); - assertEquals(1, relationshipDAO.countByItem(context, person1, false)); - assertEquals(1, relationshipDAO.countByItem(context, person1, true)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, false, false)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, false, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, false, false)); + assertEquals(1, relationshipDAO.countByItem(context, person1, false, true)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, true, false)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, true, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, true, false)); + assertEquals(1, relationshipDAO.countByItem(context, person1, true, true)); assertRelationship( relationship1, @@ -490,10 +494,14 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe assertEquals(1, relationshipService.countByItem(context, publication1)); assertEquals(1, relationshipService.countByItem(context, person1)); - assertEquals(1, relationshipService.countByItem(context, publication1, false)); - assertEquals(1, relationshipService.countByItem(context, publication1, true)); - assertEquals(1, relationshipService.countByItem(context, person1, false)); - assertEquals(1, relationshipService.countByItem(context, person1, true)); + assertEquals(1, relationshipService.countByItem(context, publication1, false, false)); + assertEquals(1, relationshipService.countByItem(context, publication1, false, true)); + assertEquals(1, relationshipService.countByItem(context, person1, false, false)); + assertEquals(1, relationshipService.countByItem(context, person1, false, true)); + assertEquals(1, relationshipService.countByItem(context, publication1, true, false)); + assertEquals(1, relationshipService.countByItem(context, publication1, true, true)); + assertEquals(1, relationshipService.countByItem(context, person1, true, false)); + assertEquals(1, relationshipService.countByItem(context, person1, true, true)); assertEquals( 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false) @@ -575,10 +583,14 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe relationshipDAO.findByItem(context, person1, -1, -1, false, true) ); - assertEquals(1, relationshipDAO.countByItem(context, publication1, false)); - assertEquals(0, relationshipDAO.countByItem(context, publication1, true)); - assertEquals(1, relationshipDAO.countByItem(context, person1, false)); - assertEquals(1, relationshipDAO.countByItem(context, person1, true)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, false, false)); + assertEquals(0, relationshipDAO.countByItem(context, publication1, false, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, false, false)); + assertEquals(1, relationshipDAO.countByItem(context, person1, false, true)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, true, false)); + assertEquals(0, relationshipDAO.countByItem(context, publication1, true, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, true, false)); + assertEquals(1, relationshipDAO.countByItem(context, person1, true, true)); assertRelationship( relationship1, @@ -762,10 +774,14 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe assertEquals(0, relationshipService.countByItem(context, publication1)); assertEquals(1, relationshipService.countByItem(context, person1)); - assertEquals(1, relationshipService.countByItem(context, publication1, false)); - assertEquals(0, relationshipService.countByItem(context, publication1, true)); - assertEquals(1, relationshipService.countByItem(context, person1, false)); - assertEquals(1, relationshipService.countByItem(context, person1, true)); + assertEquals(1, relationshipService.countByItem(context, publication1, false, false)); + assertEquals(0, relationshipService.countByItem(context, publication1, false, true)); + assertEquals(1, relationshipService.countByItem(context, person1, false, false)); + assertEquals(1, relationshipService.countByItem(context, person1, false, true)); + assertEquals(1, relationshipService.countByItem(context, publication1, true, false)); + assertEquals(0, relationshipService.countByItem(context, publication1, true, true)); + assertEquals(1, relationshipService.countByItem(context, person1, true, false)); + assertEquals(1, relationshipService.countByItem(context, person1, true, true)); assertEquals( 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false) @@ -847,10 +863,14 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe relationshipDAO.findByItem(context, person1, -1, -1, false, true) ); - assertEquals(1, relationshipDAO.countByItem(context, publication1, false)); - assertEquals(1, relationshipDAO.countByItem(context, publication1, true)); - assertEquals(1, relationshipDAO.countByItem(context, person1, false)); - assertEquals(0, relationshipDAO.countByItem(context, person1, true)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, false, false)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, false, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, false, false)); + assertEquals(0, relationshipDAO.countByItem(context, person1, false, true)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, true, false)); + assertEquals(1, relationshipDAO.countByItem(context, publication1, true, true)); + assertEquals(1, relationshipDAO.countByItem(context, person1, true, false)); + assertEquals(0, relationshipDAO.countByItem(context, person1, true, true)); assertRelationship( relationship1, @@ -1034,10 +1054,14 @@ public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTe assertEquals(1, relationshipService.countByItem(context, publication1)); assertEquals(0, relationshipService.countByItem(context, person1)); - assertEquals(1, relationshipService.countByItem(context, publication1, false)); - assertEquals(1, relationshipService.countByItem(context, publication1, true)); - assertEquals(1, relationshipService.countByItem(context, person1, false)); - assertEquals(0, relationshipService.countByItem(context, person1, true)); + assertEquals(1, relationshipService.countByItem(context, publication1, false, false)); + assertEquals(1, relationshipService.countByItem(context, publication1, false, true)); + assertEquals(1, relationshipService.countByItem(context, person1, false, false)); + assertEquals(0, relationshipService.countByItem(context, person1, false, true)); + assertEquals(1, relationshipService.countByItem(context, publication1, true, false)); + assertEquals(1, relationshipService.countByItem(context, publication1, true, true)); + assertEquals(1, relationshipService.countByItem(context, person1, true, false)); + assertEquals(0, relationshipService.countByItem(context, person1, true, true)); assertEquals( 0, relationshipService.countByItemAndRelationshipType(context, publication1, relationshipType, false) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java index 3eae86361a..4a282ee466 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java @@ -52,10 +52,10 @@ public class ItemRelationshipLinkRepository extends AbstractDSpaceRestRepository if (item == null) { throw new ResourceNotFoundException("No such item: " + itemId); } - int total = relationshipService.countByItem(context, item); + int total = relationshipService.countByItem(context, item, true, true); Pageable pageable = utils.getPageable(optionalPageable); List relationships = relationshipService.findByItem(context, item, - pageable.getPageSize(), Math.toIntExact(pageable.getOffset()), true); + pageable.getPageSize(), Math.toIntExact(pageable.getOffset()), true, true); return converter.toRestPage(relationships, pageable, total, projection); } catch (SQLException e) { throw new RuntimeException(e); From 696fcae777f43d22ffec0b7e53ad3644811a2954 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Mon, 7 Mar 2022 12:30:30 +0100 Subject: [PATCH 0734/1254] 87994: Refactor reset file after retrieved & only put in byteArray if with coverpage --- .../app/rest/BitstreamRestController.java | 5 +- .../app/rest/utils/BitstreamResource.java | 62 +++++++++++-------- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 42f0663928..183aee83d0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -132,7 +132,7 @@ public class BitstreamRestController { try { long filesize = bit.getSizeBytes(); - var citationEnabledForBitstream = citationDocumentService.isCitationEnabledForBitstream(bit, context); + Boolean citationEnabledForBitstream = citationDocumentService.isCitationEnabledForBitstream(bit, context); HttpHeadersInitializer httpHeadersInitializer = new HttpHeadersInitializer() .withBufferSize(BUFFER_SIZE) @@ -152,10 +152,9 @@ public class BitstreamRestController { httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT); } - org.dspace.app.rest.utils.BitstreamResource bitstreamResource = new org.dspace.app.rest.utils.BitstreamResource( - bit, name, uuid, currentUser != null ? currentUser.getID() : null, citationEnabledForBitstream); + name, uuid, currentUser != null ? currentUser.getID() : null, citationEnabledForBitstream); //We have all the data we need, close the connection to the database so that it doesn't stay open during //download/streaming diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java index 6de71fed59..694e6a254a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/BitstreamResource.java @@ -35,44 +35,46 @@ import org.springframework.core.io.AbstractResource; */ public class BitstreamResource extends AbstractResource { - private Bitstream bitstream; private String name; private UUID uuid; private UUID currentUserUUID; private boolean shouldGenerateCoverPage; private byte[] file; - private Long fileSize; private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); private CitationDocumentService citationDocumentService = new DSpace().getServiceManager() - .getServicesByType(CitationDocumentService.class).get(0); + .getServicesByType(CitationDocumentService.class).get(0); - public BitstreamResource(Bitstream bitstream, String name, UUID uuid, UUID currentUserUUID, + public BitstreamResource(String name, UUID uuid, UUID currentUserUUID, boolean shouldGenerateCoverPage) { - this.bitstream = bitstream; this.name = name; this.uuid = uuid; this.currentUserUUID = currentUserUUID; this.shouldGenerateCoverPage = shouldGenerateCoverPage; } - private Pair getFileData(Context context, Bitstream bitstream) throws SQLException, - AuthorizeException, IOException { - if (file == null || fileSize == null) { - if (shouldGenerateCoverPage) { - var citedDocument = citationDocumentService.makeCitedDocument(context, bitstream); + /** + * Get Potential cover page by array, this method should only be called when a coverpage should be generated + * In case of failure the original file will be returned + * + * @param context the DSpace context + * @param bitstream the pdf for which we want to generate a coverpage + * @return a byte array containing the cover page + */ + private byte[] getCoverpageByteArray(Context context, Bitstream bitstream) + throws IOException, SQLException, AuthorizeException { + if (file == null) { + try { + Pair citedDocument = citationDocumentService.makeCitedDocument(context, bitstream); this.file = citedDocument.getLeft(); - this.fileSize = citedDocument.getRight(); - } else { - var inputStream = bitstreamService.retrieve(context, bitstream); - this.file = IOUtils.toByteArray(inputStream); - inputStream.close(); - this.fileSize = bitstream.getSizeBytes(); + } catch (Exception e) { + // Return the original bitstream without the cover page + this.file = IOUtils.toByteArray(bitstreamService.retrieve(context, bitstream)); } } - return Pair.of(file, fileSize); + return file; } @Override @@ -82,20 +84,22 @@ public class BitstreamResource extends AbstractResource { @Override public InputStream getInputStream() throws IOException { - Context context = new Context(); - try { + try (Context context = new Context()) { EPerson currentUser = ePersonService.find(context, currentUserUUID); context.setCurrentUser(currentUser); Bitstream bitstream = bitstreamService.find(context, uuid); - return new ByteArrayInputStream(getFileData(context, bitstream).getLeft()); + InputStream out; + + if (shouldGenerateCoverPage) { + out = new ByteArrayInputStream(getCoverpageByteArray(context, bitstream)); + } else { + out = bitstreamService.retrieve(context, bitstream); + } + + this.file = null; + return out; } catch (SQLException | AuthorizeException e) { throw new IOException(e); - } finally { - try { - context.complete(); - } catch (SQLException e) { - throw new IOException(e); - } } } @@ -110,7 +114,11 @@ public class BitstreamResource extends AbstractResource { EPerson currentUser = ePersonService.find(context, currentUserUUID); context.setCurrentUser(currentUser); Bitstream bitstream = bitstreamService.find(context, uuid); - return getFileData(context, bitstream).getRight(); + if (shouldGenerateCoverPage) { + return getCoverpageByteArray(context, bitstream).length; + } else { + return bitstream.getSizeBytes(); + } } catch (SQLException | AuthorizeException e) { throw new IOException(e); } From b142601c62869a8b8af8ccf24fcb7a11b5fed880 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Mon, 7 Mar 2022 14:17:26 +0100 Subject: [PATCH 0735/1254] 88056: Filter relationship metadata by class instance instead of authority key --- .../java/org/dspace/versioning/AbstractVersionProvider.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 6c04657ab4..3633f1949b 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java @@ -12,7 +12,6 @@ import java.sql.SQLException; import java.util.List; import java.util.Set; -import org.apache.commons.lang3.StringUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; @@ -22,10 +21,10 @@ 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.service.BitstreamService; import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; -import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.storage.bitstore.service.BitstreamStorageService; import org.springframework.beans.factory.annotation.Autowired; @@ -58,7 +57,7 @@ public abstract class AbstractVersionProvider { String unqualifiedMetadataField = metadataSchema.getName() + "." + metadataField.getElement(); if (getIgnoredMetadataFields().contains(metadataField.toString('.')) || getIgnoredMetadataFields().contains(unqualifiedMetadataField + "." + Item.ANY) || - StringUtils.startsWith(aMd.getAuthority(), Constants.VIRTUAL_AUTHORITY_PREFIX)) { + aMd instanceof RelationshipMetadataValue) { //Skip this metadata field (ignored and/or virtual) continue; } From 2e4f920d24fafced53fd5679ebc31296f2303279 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 7 Mar 2022 15:03:18 -0600 Subject: [PATCH 0736/1254] Move "solr.authority.server" to solrauthority.cfg --- dspace/config/dspace.cfg | 4 ---- dspace/config/modules/solrauthority.cfg | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 31b4f8a6ce..168c86d64a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1413,10 +1413,6 @@ plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ ## See manual or org.dspace.content.authority.Choices source for descriptions. authority.minconfidence = ambiguous -# Solr Authority index location -# Default is ${solr.server}/authority, unless solr.multicorePrefix is specified -solr.authority.server=${solr.server}/${solr.multicorePrefix}authority - # Configuration settings for ORCID based authority control. # Uncomment the lines below to enable configuration #choices.plugin.dc.contributor.author = SolrAuthorAuthority diff --git a/dspace/config/modules/solrauthority.cfg b/dspace/config/modules/solrauthority.cfg index 9ed1855d44..afa1bdd22a 100644 --- a/dspace/config/modules/solrauthority.cfg +++ b/dspace/config/modules/solrauthority.cfg @@ -4,6 +4,10 @@ # These configs are only used by the SOLR authority index # #---------------------------------------------------------------# +# Solr Authority index location +# Default is ${solr.server}/authority, unless solr.multicorePrefix is specified +solr.authority.server=${solr.server}/${solr.multicorePrefix}authority + # Update item metadata displayed values (not the authority keys) # with the lasted cached versions when running the UpdateAuthorities index script #solrauthority.auto-update-items=false From 204d9f0047f514c94bd312a66363b7b336b0c1ba Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Tue, 8 Mar 2022 11:39:04 +0100 Subject: [PATCH 0737/1254] 88146: Fix tests --- ...eftTiltedRelationshipRestRepositoryIT.java | 164 ++++++++++++++++++ .../rest/RelationshipRestRepositoryIT.java | 8 +- 2 files changed, 170 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LeftTiltedRelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LeftTiltedRelationshipRestRepositoryIT.java index e50d0f4654..480ca7b18d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LeftTiltedRelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LeftTiltedRelationshipRestRepositoryIT.java @@ -7,16 +7,25 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; +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.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.concurrent.atomic.AtomicReference; + +import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; +import org.dspace.content.Item; import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.RelationshipType; import org.junit.Before; import org.junit.Test; +import org.springframework.http.MediaType; /** * This class carries out the same test cases as {@link RelationshipRestRepositoryIT}. @@ -75,4 +84,159 @@ public class LeftTiltedRelationshipRestRepositoryIT extends RelationshipRestRepo ))); } + /** + * This method will test the deletion of a Relationship and will then + * verify that the relation is removed + * @throws Exception + */ + @Override + @Test + public void deleteRelationship() throws Exception { + context.turnOffAuthorisationSystem(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2016-02-13") + .withPersonIdentifierFirstName("Maria") + .withPersonIdentifierLastName("Smith") + .build(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // First create 1 relationship. + context.restoreAuthSystemState(); + + AtomicReference idRef1 = new AtomicReference<>(); + AtomicReference idRef2 = new AtomicReference<>(); + try { + // This post request will add a first relationship to the publication and thus create + // a first set of metadata for the author values, namely "Donald Smith" + getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core" + + "/items/" + publication1 + .getID() + "\n" + + "https://localhost:8080/spring-rest/api/core" + + "/items/" + author1 + .getID())) + .andExpect(status().isCreated()) + .andDo(result -> idRef1.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // This test checks that there's one relationship on the publication + getClient(adminToken).perform(get("/api/core/items/" + + publication1.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(1))); + + // This test checks that there's NO relationship on the first author + // NOTE: relationship is excluded because of tilted left + getClient(adminToken).perform(get("/api/core/items/" + + author1.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + + // This test checks that there's no relationship on the second author + getClient(adminToken).perform(get("/api/core/items/" + + author2.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + + // Creates another Relationship for the Publication + getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication1 + .getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2 + .getID())) + .andExpect(status().isCreated()) + .andDo(result -> idRef2.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // This test checks that there are 2 relationships on the publication + getClient(adminToken).perform(get("/api/core/items/" + + publication1.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(2))); + + // This test checks that there's no relationship on the first author + // NOTE: relationship is excluded because of tilted left + getClient(adminToken).perform(get("/api/core/items/" + + author1.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + + // This test checks that there's no relationship on the second author + // NOTE: relationship is excluded because of tilted left + getClient(adminToken).perform(get("/api/core/items/" + + author2.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + + + // Now we delete the first relationship + getClient(adminToken).perform(delete("/api/core/relationships/" + idRef1)); + + + // This test checks that there's one relationship on the publication + getClient(adminToken).perform(get("/api/core/items/" + + publication1.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(1))); + + // This test checks that there's no relationship on the first author + getClient(adminToken).perform(get("/api/core/items/" + + author1.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + + // This test checks that there's no relationship on the second author + // NOTE: relationship is excluded because of tilted left + getClient(adminToken).perform(get("/api/core/items/" + + author2.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + + + // Now we delete the second relationship + getClient(adminToken).perform(delete("/api/core/relationships/" + idRef2)); + + + // This test checks that there's no relationship on the publication + getClient(adminToken).perform(get("/api/core/items/" + + publication1.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + + // This test checks that there's no relationship on the first author + getClient(adminToken).perform(get("/api/core/items/" + + author1.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + + // This test checks that there are no relationship on the second author + getClient(adminToken).perform(get("/api/core/items/" + + author2.getID() + "/relationships")) + .andExpect(status().isOk()) + .andExpect(jsonPath("page.totalElements", is(0))); + } finally { + if (idRef1.get() != null) { + RelationshipBuilder.deleteRelationship(idRef1.get()); + } + if (idRef2.get() != null) { + RelationshipBuilder.deleteRelationship(idRef2.get()); + } + } + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index cdd9c7f2c3..7ec5a065e9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -1524,8 +1524,12 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest .andExpect(status().isOk()) .andExpect(jsonPath("page.totalElements", is(0))); } finally { - RelationshipBuilder.deleteRelationship(idRef1.get()); - RelationshipBuilder.deleteRelationship(idRef2.get()); + if (idRef1.get() != null) { + RelationshipBuilder.deleteRelationship(idRef1.get()); + } + if (idRef2.get() != null) { + RelationshipBuilder.deleteRelationship(idRef2.get()); + } } } From 2d7a52dc005323df0d41cc723c5063a72a44469d Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 8 Mar 2022 13:24:53 +0100 Subject: [PATCH 0738/1254] 88141: Scripts and processes bug #7992 --- .../app/rest/converter/ScriptConverter.java | 23 ++++++++- .../config/spring/rest/scripts.xml | 8 +++- .../app/rest/ScriptRestRepositoryIT.java | 43 +++++++++++++++++ .../app/scripts/TypeConversionTestScript.java | 33 +++++++++++++ ...TypeConversionTestScriptConfiguration.java | 48 +++++++++++++++++++ 5 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScript.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScriptConfiguration.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScriptConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScriptConverter.java index 105975355b..9c9957a55b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScriptConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScriptConverter.java @@ -12,6 +12,7 @@ import java.util.List; import org.apache.commons.cli.Option; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang.StringUtils; import org.dspace.app.rest.model.ParameterRest; import org.dspace.app.rest.model.ScriptRest; import org.dspace.app.rest.projection.Projection; @@ -39,7 +40,7 @@ public class ScriptConverter implements DSpaceConverter getModelClass() { return ScriptConfiguration.class; diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml index ab9826b84d..4e88ef2763 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/rest/scripts.xml @@ -27,4 +27,10 @@ - \ No newline at end of file + + + + + + + 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 9976fb4be1..48cd26227c 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 @@ -8,6 +8,8 @@ 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.hasItem; import static org.hamcrest.Matchers.is; @@ -484,6 +486,47 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { } } + @Test + public void scriptTypeConversionTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/scripts/type-conversion-test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ScriptMatcher + .matchScript("type-conversion-test", + "Test the type conversion different option types"))) + .andExpect(jsonPath("$.parameters", containsInAnyOrder( + allOf( + hasJsonPath("$.name", is("-b")), + hasJsonPath("$.description", is("option set to the boolean class")), + hasJsonPath("$.type", is("boolean")), + hasJsonPath("$.mandatory", is(false)), + hasJsonPath("$.nameLong", is("--boolean")) + ), + allOf( + hasJsonPath("$.name", is("-s")), + hasJsonPath("$.description", is("string option with an argument")), + hasJsonPath("$.type", is("String")), + hasJsonPath("$.mandatory", is(false)), + hasJsonPath("$.nameLong", is("--string")) + ), + allOf( + hasJsonPath("$.name", is("-n")), + hasJsonPath("$.description", is("string option without an argument")), + hasJsonPath("$.type", is("boolean")), + hasJsonPath("$.mandatory", is(false)), + hasJsonPath("$.nameLong", is("--noargument")) + ), + allOf( + hasJsonPath("$.name", is("-f")), + hasJsonPath("$.description", is("file option with an argument")), + hasJsonPath("$.type", is("InputStream")), + hasJsonPath("$.mandatory", is(false)), + hasJsonPath("$.nameLong", is("--file")) + ) + ) )); + } + @After public void destroy() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScript.java b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScript.java new file mode 100644 index 0000000000..824e5dda9f --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScript.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.scripts; + +import org.apache.commons.cli.ParseException; +import org.dspace.app.rest.converter.ScriptConverter; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; + +/** + * Script used to test the type conversion in the {@link ScriptConverter} + */ +public class TypeConversionTestScript extends DSpaceRunnable { + + + public TypeConversionTestScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager() + .getServiceByName("type-conversion-test", TypeConversionTestScriptConfiguration.class); + } + + public void setup() throws ParseException { + // This script is only used to test rest exposure, no setup is required. + } + + public void internalRun() throws Exception { + // This script is only used to test rest exposure, no internal run is required. + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScriptConfiguration.java b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScriptConfiguration.java new file mode 100644 index 0000000000..5738748e0b --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/TypeConversionTestScriptConfiguration.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.app.scripts; + +import java.io.InputStream; + +import org.apache.commons.cli.Options; +import org.dspace.app.rest.converter.ScriptConverter; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; + +/** + * Script configuration used to test the type conversion in the {@link ScriptConverter} + */ +public class TypeConversionTestScriptConfiguration extends ScriptConfiguration { + + + public Class getDspaceRunnableClass() { + return null; + } + + public void setDspaceRunnableClass(final Class dspaceRunnableClass) { + + } + + public boolean isAllowedToExecute(final Context context) { + return true; + } + + public Options getOptions() { + + Options options = new Options(); + + options.addOption("b", "boolean", false, "option set to the boolean class"); + options.getOption("b").setType(boolean.class); + options.addOption("s", "string", true, "string option with an argument"); + options.addOption("n", "noargument", false, "string option without an argument"); + options.addOption("f", "file", true, "file option with an argument"); + options.getOption("f").setType(InputStream.class); + + return options; + } +} From 020ebbd3d161e68cb35167912238a1081cf1e5f6 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 9 Mar 2022 10:55:09 +0100 Subject: [PATCH 0739/1254] 88061: Refactor VersioningConsumer --- .../dspace/versioning/VersioningConsumer.java | 105 +++++++++++++----- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java index 6683419844..11eb977f96 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java @@ -10,7 +10,10 @@ package org.dspace.versioning; import java.util.HashSet; import java.util.Set; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; +import org.dspace.content.Relationship; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; @@ -19,26 +22,28 @@ import org.dspace.event.Consumer; import org.dspace.event.Event; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; -import org.dspace.versioning.service.VersioningService; /** + * When a new version of an item is published, unarchive the previous version and + * update {@link Relationship#latestVersionStatus} of the relevant relationships. + * * @author Fabio Bolognesi (fabio at atmire dot com) * @author Mark Diggory (markd at atmire dot com) * @author Ben Bosman (ben at atmire dot com) */ public class VersioningConsumer implements Consumer { - private static Set itemsToProcess; + private static final Logger log = LogManager.getLogger(VersioningConsumer.class); + + private Set itemsToProcess; private VersionHistoryService versionHistoryService; - private VersioningService versioningService; private ItemService itemService; @Override public void initialize() throws Exception { versionHistoryService = VersionServiceFactory.getInstance().getVersionHistoryService(); - versioningService = VersionServiceFactory.getInstance().getVersionService(); itemService = ContentServiceFactory.getInstance().getItemService(); } @@ -49,35 +54,75 @@ public class VersioningConsumer implements Consumer { @Override public void consume(Context ctx, Event event) throws Exception { if (itemsToProcess == null) { - itemsToProcess = new HashSet(); + itemsToProcess = new HashSet<>(); } - int st = event.getSubjectType(); - int et = event.getEventType(); - - if (st == Constants.ITEM && et == Event.INSTALL) { - Item item = (Item) event.getSubject(ctx); - if (item != null && item.isArchived()) { - VersionHistory history = versionHistoryService.findByItem(ctx, item); - if (history != null) { - Version latest = versionHistoryService.getLatestVersion(ctx, history); - Version previous = versionHistoryService.getPrevious(ctx, history, latest); - if (previous != null) { - Item previousItem = previous.getItem(); - if (previousItem != null) { - previousItem.setArchived(false); - itemsToProcess.add(previousItem); - //Fire a new modify event for our previous item - //Due to the need to reindex the item in the search - //and browse index we need to fire a new event - ctx.addEvent(new Event(Event.MODIFY, - previousItem.getType(), previousItem.getID(), - null, itemService.getIdentifiers(ctx, previousItem))); - } - } - } - } + // only items + if (event.getSubjectType() != Constants.ITEM) { + return; } + + // only install events + if (event.getEventType() != Event.INSTALL) { + return; + } + + // get the item (should be archived) + Item item = (Item) event.getSubject(ctx); + if (item == null || !item.isArchived()) { + return; + } + + // get version history + VersionHistory history = versionHistoryService.findByItem(ctx, item); + if (history == null) { + return; + } + + // get latest version + Version latestVersion = versionHistoryService.getLatestVersion(ctx, history); + if (latestVersion == null) { + return; + } + + // get previous version + Version previousVersion = versionHistoryService.getPrevious(ctx, history, latestVersion); + if (previousVersion == null) { + return; + } + + // get latest item + Item latestItem = latestVersion.getItem(); + if (latestItem == null) { + String msg = String.format( + "Illegal state: Obtained version history of item with uuid %s, handle %s, but the latest item is null", + item.getID(), item.getHandle() + ); + log.error(msg); + throw new IllegalStateException(msg); + } + + // get previous item + Item previousItem = previousVersion.getItem(); + if (previousItem == null) { + return; + } + + // unarchive previous item + unarchiveItem(ctx, previousItem); + + // TODO implement w2p 88061 + } + + protected void unarchiveItem(Context ctx, Item item) { + item.setArchived(false); + itemsToProcess.add(item); + //Fire a new modify event for our previous item + //Due to the need to reindex the item in the search + //and browse index we need to fire a new event + ctx.addEvent(new Event( + Event.MODIFY, item.getType(), item.getID(), null, itemService.getIdentifiers(ctx, item) + )); } @Override From 4d8a81d3bd6fde6c02f074e7c1604b143f4b3a5e Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Wed, 9 Mar 2022 14:35:00 +0100 Subject: [PATCH 0740/1254] Filter browse-by-item lists instead of jumping to prefix --- .../java/org/dspace/browse/BrowseEngine.java | 16 +++++++++------- .../java/org/dspace/browse/SolrBrowseDAO.java | 3 +++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index 302d46eb0d..cd6058d99b 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -203,6 +203,8 @@ public class BrowseEngine { // get the table name that we are going to be getting our data from dao.setTable(browseIndex.getTableName()); + dao.setStartsWith(StringUtils.lowerCase(scope.getStartsWith())); + // tell the browse query whether we are ascending or descending on the value dao.setAscending(scope.isAscending()); @@ -249,9 +251,6 @@ public class BrowseEngine { } } - // this is the total number of results in answer to the query - int total = getTotalResults(); - // assemble the ORDER BY clause String orderBy = browseIndex.getSortField(scope.isSecondLevel()); if (scope.getSortBy() > 0) { @@ -259,6 +258,9 @@ public class BrowseEngine { } dao.setOrderField(orderBy); + // this is the total number of results in answer to the query + int total = getTotalResults(); + int offset = scope.getOffset(); String rawFocusValue = null; if (offset < 1 && (scope.hasJumpToItem() || scope.hasJumpToValue() || scope.hasStartsWith())) { @@ -272,7 +274,7 @@ public class BrowseEngine { log.debug("browsing using focus: " + focusValue); // Convert the focus value into an offset - offset = getOffsetForValue(focusValue); + // offset = getOffsetForValue(focusValue); } dao.setOffset(offset); @@ -685,13 +687,13 @@ public class BrowseEngine { // our count, storing them locally to reinstate later String focusField = dao.getJumpToField(); String focusValue = dao.getJumpToValue(); - String orderField = dao.getOrderField(); + // String orderField = dao.getOrderField(); int limit = dao.getLimit(); int offset = dao.getOffset(); dao.setJumpToField(null); dao.setJumpToValue(null); - dao.setOrderField(null); + // dao.setOrderField(null); dao.setLimit(-1); dao.setOffset(-1); @@ -701,7 +703,7 @@ public class BrowseEngine { // now put back the values we removed for this method dao.setJumpToField(focusField); dao.setJumpToValue(focusValue); - dao.setOrderField(orderField); + // dao.setOrderField(orderField); dao.setLimit(limit); dao.setOffset(offset); dao.setCountValues(null); 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 6a960e8d75..391ed90771 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -205,6 +205,9 @@ public class SolrBrowseDAO implements BrowseDAO { } else if (valuePartial) { query.addFilterQueries("{!field f=" + facetField + "_partial}" + value); } + if (StringUtils.isNotBlank(startsWith) && orderField != null) { + query.addFilterQueries("bi_" + orderField + "_sort:" + startsWith + "*"); + } // filter on item to be sure to don't include any other object // indexed in the Discovery Search core query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); From 0b9b16a14a8eb3c46c472c77a9098b9dfa3bc7db Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 9 Mar 2022 19:37:10 +0100 Subject: [PATCH 0741/1254] 88061: Implement relationship latest version status change (untested) --- .../dspace/versioning/VersioningConsumer.java | 356 +++++++++++++++++- 1 file changed, 354 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java index 11eb977f96..9314bad850 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java @@ -7,15 +7,27 @@ */ package org.dspace.versioning; +import java.sql.SQLException; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.content.EntityType; import org.dspace.content.Item; +import org.dspace.content.MetadataValue; import org.dspace.content.Relationship; +import org.dspace.content.Relationship.LatestVersionStatus; +import org.dspace.content.RelationshipType; import org.dspace.content.factory.ContentServiceFactory; +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.core.Constants; import org.dspace.core.Context; import org.dspace.event.Consumer; @@ -39,12 +51,17 @@ public class VersioningConsumer implements Consumer { private VersionHistoryService versionHistoryService; private ItemService itemService; - + private EntityTypeService entityTypeService; + private RelationshipTypeService relationshipTypeService; + private RelationshipService relationshipService; @Override public void initialize() throws Exception { versionHistoryService = VersionServiceFactory.getInstance().getVersionHistoryService(); itemService = ContentServiceFactory.getInstance().getItemService(); + entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); + relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); } @Override @@ -111,7 +128,8 @@ public class VersioningConsumer implements Consumer { // unarchive previous item unarchiveItem(ctx, previousItem); - // TODO implement w2p 88061 + // update relationships + updateRelationships(ctx, latestItem, previousItem); } protected void unarchiveItem(Context ctx, Item item) { @@ -125,6 +143,340 @@ public class VersioningConsumer implements Consumer { )); } + protected void updateRelationships(Context ctx, Item latestItem, Item previousItem) { + // check that the entity types of both items match + if (!doEntityTypesMatch(latestItem, previousItem)) { + return; + } + + // get the entity type (same for both items) + EntityType entityType = getEntityType(ctx, latestItem); + if (entityType == null) { + return; + } + + // get all relationship types that are linked to the given entity type + List relationshipTypes = getRelationshipTypes(ctx, entityType); + if (CollectionUtils.isEmpty(relationshipTypes)) { + return; + } + + for (RelationshipType relationshipType : relationshipTypes) { + List latestItemRelationships = getAllRelationships(ctx, latestItem, relationshipType); + if (latestItemRelationships == null) { + continue; + } + + List previousItemRelationships = getAllRelationships(ctx, previousItem, relationshipType); + if (previousItemRelationships == null) { + continue; + } + + for (Relationship previousItemRelationship : previousItemRelationships) { + // determine on which side of the relationship the latest and previous item should be + boolean isLeft = previousItem.equals(previousItemRelationship.getLeftItem()); + boolean isRight = previousItem.equals(previousItemRelationship.getRightItem()); + if (isLeft == isRight) { + Item leftItem = previousItemRelationship.getLeftItem(); + Item rightItem = previousItemRelationship.getRightItem(); + String msg = String.format( + "Illegal state: could not determine side of item with uuid %s, handle %s in " + + "relationship with id %s, rightward name %s between " + + "left item with uuid %s, handle %s and right item with uuid %s, handle %s", + previousItem.getID(), previousItem.getHandle(), previousItemRelationship.getID(), + previousItemRelationship.getRelationshipType().getRightwardType(), + leftItem.getID(), leftItem.getHandle(), rightItem.getID(), rightItem.getHandle() + ); + log.error(msg); + throw new IllegalStateException(msg); + } + + // get the matching relationship on the latest item + Relationship latestItemRelationship = + getMatchingRelationship(latestItem, isLeft, previousItemRelationship, latestItemRelationships); + + // for sure set the previous item to non-latest + // NOTE: if no matching relationship exists, this relationship will be considered deleted + // when viewed from the other item + updateLatestVersionStatus(previousItemRelationship, isLeft, false); + + // set the new item to latest if the relevant relationship exists + if (latestItemRelationship != null) { + updateLatestVersionStatus(latestItemRelationship, isLeft, true); + } + } + } + } + + /** + * Given two items, check if their entity types match. + * If one or both items don't have an entity type, comparing is pointless and this method will return false. + * @param latestItem the item that represents the most recent version. + * @param previousItem the item that represents the second-most recent version. + * @return true if the entity types of both items are non-null and equal, false otherwise. + */ + protected boolean doEntityTypesMatch(Item latestItem, Item previousItem) { + String latestItemEntityType = getEntityType(latestItem); + String previousItemEntityType = getEntityType(previousItem); + + // check if both items have an entity type + if (latestItemEntityType == null || previousItemEntityType == null) { + if (previousItemEntityType != null) { + log.warn(String.format( + "Inconsistency: Item with uuid %s, handle %s has NO entity type, " + + "but the previous version of that item with uuid %s, handle %s has entity type %s", + latestItem.getID(), latestItem.getHandle(), + previousItem.getID(), previousItem.getHandle(), previousItemEntityType + )); + } + + // one or both items do not have an entity type, so comparing is pointless + return false; + } + + // check if the entity types are equal + if (!StringUtils.equals(latestItemEntityType, previousItemEntityType)) { + log.warn(String.format( + "Inconsistency: Item with uuid %s, handle %s has entity type %s, " + + "but the previous version of that item with uuid %s, handle %s has entity type %s", + latestItem.getID(), latestItem.getHandle(), latestItemEntityType, + previousItem.getID(), previousItem.getHandle(), previousItemEntityType + )); + return false; + } + + // success - the entity types of both items are non-null and equal + log.info(String.format( + "Item with uuid %s, handle %s and the previous version of that item with uuid %s, handle %s " + + "have the same entity type: %s", + latestItem.getID(), latestItem.getHandle(), previousItem.getID(), previousItem.getHandle(), + latestItemEntityType + )); + return true; + } + + /** + * Get the entity type (stored in metadata field dspace.entity.type) of any item. + * @param item the item. + * @return the label of the entity type. + */ + protected String getEntityType(Item item) { + List mdvs = itemService.getMetadata(item, "dspace", "entity", "type", Item.ANY, false); + if (mdvs.isEmpty()) { + return null; + } + if (mdvs.size() > 1) { + log.warn(String.format( + "Item with uuid %s, handle %s has %s entity types (%s), expected 1 entity type", + item.getID(), item.getHandle(), mdvs.size(), + mdvs.stream().map(MetadataValue::getValue).collect(Collectors.toUnmodifiableList()) + )); + } + + String entityType = mdvs.get(0).getValue(); + if (StringUtils.isBlank(entityType)) { + return null; + } + + return entityType; + } + + /** + * Get the entity type (stored in metadata field dspace.entity.type) of any item. + * @param item the item. + * @return the entity type. + */ + protected EntityType getEntityType(Context ctx, Item item) { + String entityTypeStr = getEntityType(item); + if (entityTypeStr == null) { + return null; + } + + try { + return entityTypeService.findByEntityType(ctx, entityTypeStr); + } catch (SQLException e) { + log.error(String.format( + "Exception occurred when trying to obtain entity type with label %s of item with uuid %s, handle %s", + entityTypeStr, item.getID(), item.getHandle() + ), e); + return null; + } + } + + /** + * Get all relationship types that have the given entity type on their left and/or right side. + * @param ctx the DSpace context. + * @param entityType the entity type for which all relationship types should be found. + * @return a list of relationship types (possibly empty), or null in case of error. + */ + protected List getRelationshipTypes(Context ctx, EntityType entityType) { + try { + return relationshipTypeService.findByEntityType(ctx, entityType); + } catch (SQLException e) { + log.error(String.format( + "Exception occurred when trying to obtain relationship types via entity type with id %s, label %s", + entityType.getID(), entityType.getLabel() + ), e); + return null; + } + } + + /** + * Get all relationships of the given type linked to the given item. + * @param ctx the DSpace context. + * @param item the item. + * @param relationshipType the relationship type. + * @return a list of relationships (possibly empty), or null in case of error. + */ + protected List getAllRelationships(Context ctx, Item item, RelationshipType relationshipType) { + try { + return relationshipService.findByItemAndRelationshipType(ctx, item, relationshipType, -1, -1, false); + } catch (SQLException e) { + log.error(String.format( + "Exception occurred when trying to obtain relationships of type with id %s, rightward name %s " + + "for item with uuid %s, handle %s", + relationshipType.getID(), relationshipType.getRightwardType(), item.getID(), item.getHandle() + ), e); + return null; + } + } + + /** + * From a list of relationships, find the relationship with the correct relationship type and items. + * If isLeft is true, the provided item should be on the left side of the relationship. + * If isLeft is false, the provided item should be on the right side of the relationship. + * In both cases, the other item is taken from the given relationship. + * @param latestItem the item that should either be on the left or right side of the returned relationship (if any). + * @param isLeft decide on which side of the relationship the provided item should be. + * @param previousItemRelationship the relationship from which the type and the other item are read. + * @param relationships the list of relationships that we'll search through. + * @return the relationship that satisfies the requirements (can only be one or zero). + */ + protected Relationship getMatchingRelationship( + Item latestItem, boolean isLeft, Relationship previousItemRelationship, List relationships + ) { + Item leftItem = previousItemRelationship.getLeftItem(); + RelationshipType relationshipType = previousItemRelationship.getRelationshipType(); + Item rightItem = previousItemRelationship.getRightItem(); + + if (isLeft) { + return getMatchingRelationship(latestItem, relationshipType, rightItem, relationships); + } else { + return getMatchingRelationship(leftItem, relationshipType, latestItem, relationships); + } + } + + + /** + * Find the relationship with the given left item, relation type and right item, from a list of relationships. + * @param expectedLeftItem the relationship that we're looking for has this item on the left side. + * @param expectedRelationshipType the relationship that we're looking for has this relationship type. + * @param expectedRightItem the relationship that we're looking for has this item on the right side. + * @param relationships the list of relationships that we'll search through. + * @return the relationship that satisfies the requirements (can only be one or zero). + */ + protected Relationship getMatchingRelationship( + Item expectedLeftItem, RelationshipType expectedRelationshipType, Item expectedRightItem, + List relationships + ) { + Integer expectedRelationshipTypeId = expectedRelationshipType.getID(); + + List matchingRelationships = relationships.stream() + .filter(relationship -> { + int relationshipTypeId = relationship.getID(); + + boolean leftItemMatches = expectedLeftItem.equals(relationship.getLeftItem()); + boolean relationshipTypeMatches = expectedRelationshipTypeId == relationshipTypeId; + boolean rightItemMatches = expectedRightItem.equals(relationship.getRightItem()); + + return leftItemMatches && relationshipTypeMatches && rightItemMatches; + }) + .distinct() + .collect(Collectors.toUnmodifiableList()); + + if (matchingRelationships.isEmpty()) { + return null; + } + + // NOTE: this situation should never occur because the relationship table has a unique constraint + // over the "left_id", "type_id" and "right_id" columns + if (matchingRelationships.size() > 1) { + String msg = String.format( + "Illegal state: expected 0 or 1 relationship, but found %s relationships (ids: %s) " + + "of type with id %s, rightward name %s " + + "between left item with uuid %s, handle %s and right item with uuid %s, handle %s", + matchingRelationships.size(), + matchingRelationships.stream().map(Relationship::getID).collect(Collectors.toUnmodifiableList()), + expectedRelationshipTypeId, expectedRelationshipType.getRightwardType(), + expectedLeftItem.getID(), expectedLeftItem.getHandle(), + expectedRightItem.getID(), expectedRightItem.getHandle() + ); + log.error(msg); + throw new IllegalStateException(msg); + } + + return matchingRelationships.get(0); + } + + /** + * Update {@link Relationship#latestVersionStatus} of the given relationship. + * @param relationship the relationship. + * @param updateLeftSide whether the status of the left item or the right item should be updated. + * @param isLatest to what the status should be set. + * @throws IllegalStateException if the operation would result in both the left side and the right side + * being set to non-latest. + */ + protected void updateLatestVersionStatus( + Relationship relationship, boolean updateLeftSide, boolean isLatest + ) throws IllegalStateException { + LatestVersionStatus lvs = relationship.getLatestVersionStatus(); + + boolean leftSideIsLatest = lvs == LatestVersionStatus.BOTH || lvs == LatestVersionStatus.LEFT_ONLY; + boolean rightSideIsLatest = lvs == LatestVersionStatus.BOTH || lvs == LatestVersionStatus.RIGHT_ONLY; + + if (updateLeftSide) { + if (leftSideIsLatest == isLatest) { + return; // no change needed + } + leftSideIsLatest = isLatest; + } else { + if (rightSideIsLatest == isLatest) { + return; // no change needed + } + rightSideIsLatest = isLatest; + } + + LatestVersionStatus newVersionStatus; + if (leftSideIsLatest && rightSideIsLatest) { + newVersionStatus = LatestVersionStatus.BOTH; + } else if (leftSideIsLatest) { + newVersionStatus = LatestVersionStatus.LEFT_ONLY; + } else if (rightSideIsLatest) { + newVersionStatus = LatestVersionStatus.RIGHT_ONLY; + } else { + String msg = String.format( + "Illegal state: cannot set %s item to latest = false, because relationship with id %s, " + + "rightward name %s between left item with uuid %s, handle %s and right item with uuid %s, handle %s " + + "has latest version status set to %s", + updateLeftSide ? "left" : "right", relationship.getID(), + relationship.getRelationshipType().getRightwardType(), + relationship.getLeftItem().getID(), relationship.getLeftItem().getHandle(), + relationship.getRightItem().getID(), relationship.getRightItem().getHandle(), lvs + ); + log.error(msg); + throw new IllegalStateException(msg); + } + + log.info(String.format( + "set latest version status from %s to %s for relationship with id %s, rightward name %s " + + "between left item with uuid %s, handle %s and right item with uuid %s, handle %s", + lvs, newVersionStatus, relationship.getID(), relationship.getRelationshipType().getRightwardType(), + relationship.getLeftItem().getID(), relationship.getLeftItem().getHandle(), + relationship.getRightItem().getID(), relationship.getRightItem().getHandle() + )); + relationship.setLatestVersionStatus(newVersionStatus); + } + @Override public void end(Context ctx) throws Exception { if (itemsToProcess != null) { From 87e3fdef983457463ceff9cd7bad1d809129c8c4 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Thu, 10 Mar 2022 10:05:40 +0100 Subject: [PATCH 0742/1254] Adjust tests to expect filtering instead of jumping --- .../app/rest/BrowsesResourceControllerIT.java | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 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 bd9e7d9356..5bea017a9e 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 @@ -897,8 +897,8 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe // ---- BROWSES BY ITEM ---- //** WHEN ** //An anonymous user browses the items in the Browse by date issued endpoint - //with startsWith set to 1990 - getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990") + //with startsWith set to 199 + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=199") .param("size", "2")) //** THEN ** @@ -907,12 +907,11 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - //We expect the totalElements to be the 7 items present in the repository - .andExpect(jsonPath("$.page.totalElements", is(7))) + //We expect the totalElements to be the 2 items present in the repository + .andExpect(jsonPath("$.page.totalElements", is(2))) //We expect to jump to page 1 of the index - .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$._links.first.href", containsString("startsWith=1990"))) //Verify that the index jumps to the "Python" item. .andExpect(jsonPath("$._embedded.items", @@ -933,19 +932,16 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - //We expect the totalElements to be the 7 items present in the repository - .andExpect(jsonPath("$.page.totalElements", is(7))) + //We expect the totalElements to be the 1 item present in the repository + .andExpect(jsonPath("$.page.totalElements", is(1))) //We expect to jump to page 2 in the index - .andExpect(jsonPath("$.page.number", is(2))) + .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._links.self.href", containsString("startsWith=T"))) //Verify that the index jumps to the "T-800" item. .andExpect(jsonPath("$._embedded.items", contains(ItemMatcher.matchItemWithTitleAndDateIssued(item7, - "T-800", "2029"), - ItemMatcher.matchItemWithTitleAndDateIssued(item5, - "Zeta Reticuli", - "2018-01-01") + "T-800", "2029") ))); //** WHEN ** @@ -961,8 +957,8 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - //We expect the totalElements to be the 3 items present in the collection - .andExpect(jsonPath("$.page.totalElements", is(3))) + //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 .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._links.self.href", containsString("startsWith=Blade"))) @@ -971,9 +967,7 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe .andExpect(jsonPath("$._embedded.items", contains(ItemMatcher.matchItemWithTitleAndDateIssued(item2, "Blade Runner", - "1982-06-25"), - ItemMatcher.matchItemWithTitleAndDateIssued(item3, - "Python", "1990") + "1982-06-25") ))); } @@ -1048,9 +1042,9 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //** WHEN ** //An anonymous user browses the items in the Browse by date issued endpoint - //with startsWith set to 1990 and Page to 3 - getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=1990") - .param("size", "2").param("page", "2")) + //with startsWith set to 199 and Page to 1 + getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=199") + .param("size", "1").param("page", "1")) //** THEN ** //The status has to be 200 OK @@ -1058,20 +1052,18 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - //We expect the totalElements to be the 7 items present in the repository - .andExpect(jsonPath("$.page.totalElements", is(7))) + //We expect the totalElements to be the 2 items present in the repository + .andExpect(jsonPath("$.page.totalElements", is(2))) //We expect to jump to page 1 of the index - .andExpect(jsonPath("$.page.number", is(2))) - .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$._links.self.href", containsString("startsWith=1990"))) + .andExpect(jsonPath("$.page.number", is(1))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$._links.self.href", containsString("startsWith=199"))) - //Verify that the index jumps to the "Zeta Reticuli" item. + //Verify that the index jumps to the "Java" item. .andExpect(jsonPath("$._embedded.items", - contains(ItemMatcher.matchItemWithTitleAndDateIssued(item7, - "Zeta Reticuli", "2018-01-01"), - ItemMatcher.matchItemWithTitleAndDateIssued(item4, - "Moon", "2018-01-02") - ))); + contains( + ItemMatcher.matchItemWithTitleAndDateIssued(item3, "Java", "1995-05-23") + ))); } @Test From f75d680ce3b55af34782f9ab7b60fa5570ff61db Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Thu, 10 Mar 2022 19:15:04 +0100 Subject: [PATCH 0743/1254] 88061: Test relationship support of VersioningConsumer --- .../dspace/versioning/VersioningConsumer.java | 2 +- .../VersioningWithRelationshipsTest.java | 563 +++++++++++++++++- 2 files changed, 552 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java index 9314bad850..b9489d1b22 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java @@ -383,7 +383,7 @@ public class VersioningConsumer implements Consumer { List matchingRelationships = relationships.stream() .filter(relationship -> { - int relationshipTypeId = relationship.getID(); + int relationshipTypeId = relationship.getRelationshipType().getID(); boolean leftItemMatches = expectedLeftItem.equals(relationship.getLeftItem()); boolean relationshipTypeMatches = expectedRelationshipTypeId == relationshipTypeId; diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index 449749aa99..a3144a85bc 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -11,9 +11,11 @@ import static org.dspace.content.Relationship.LatestVersionStatus; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import java.util.List; @@ -26,7 +28,9 @@ import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.InstallItemService; import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.WorkspaceItemService; import org.dspace.versioning.Version; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersioningService; @@ -36,8 +40,14 @@ import org.junit.Test; public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWithDatabase { - private RelationshipService relationshipService; - private VersioningService versioningService; + private final RelationshipService relationshipService = + ContentServiceFactory.getInstance().getRelationshipService(); + private final VersioningService versioningService = + VersionServiceFactory.getInstance().getVersionService(); + private final WorkspaceItemService workspaceItemService = + ContentServiceFactory.getInstance().getWorkspaceItemService(); + private final InstallItemService installItemService = + ContentServiceFactory.getInstance().getInstallItemService(); protected Community community; protected Collection collection; @@ -56,9 +66,6 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith public void setUp() throws Exception { super.setUp(); - relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); - versioningService = VersionServiceFactory.getInstance().getVersionService(); - context.turnOffAuthorisationSystem(); community = CommunityBuilder.createCommunity(context) @@ -93,7 +100,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith isProjectOfPublication = RelationshipTypeBuilder.createRelationshipTypeBuilder( context, publicationEntityType, projectEntityType, "isProjectOfPublication", "isPublicationOfProject", - 0, null, 0, null + null, null, null, null ) .withCopyToLeft(false) .withCopyToRight(false) @@ -102,7 +109,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith isOrgUnitOfPublication = RelationshipTypeBuilder.createRelationshipTypeBuilder( context, publicationEntityType, orgUnitEntityType, "isOrgUnitOfPublication", "isPublicationOfOrgUnit", - 0, null, 0, null + null, null, null, null ) .withCopyToLeft(false) .withCopyToRight(false) @@ -307,18 +314,449 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith )) ); + //////////////////////////////////////// + // do item install on new publication // + //////////////////////////////////////// + + WorkspaceItem newPublicationWSI = workspaceItemService.findByItem(context, newPublication); + installItemService.installItem(context, newPublicationWSI); + context.dispatchEvents(); + + /////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = true) // + /////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + //////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = false) // + //////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + ////////////// // clean up // ////////////// - versioningService.removeVersion(context, newPublication); + // need to manually delete all relationships to avoid SQL constraint violation exception + List relationships = relationshipService.findAll(context); + for (Relationship relationship : relationships) { + relationshipService.delete(context, relationship); + } + } + + @Test + public void test_createNewVersionOfItemAndModifyRelationships() throws Exception { + /////////////////////////////////////////////// + // create a publication with 3 relationships // + /////////////////////////////////////////////// + + Item person1 = ItemBuilder.createItem(context, collection) + .withTitle("person 1") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .build(); + + Item project1 = ItemBuilder.createItem(context, collection) + .withTitle("project 1") + .withMetadata("dspace", "entity", "type", projectEntityType.getLabel()) + .build(); + + Item orgUnit1 = ItemBuilder.createItem(context, collection) + .withTitle("org unit 1") + .withMetadata("dspace", "entity", "type", orgUnitEntityType.getLabel()) + .build(); + + Item originalPublication = ItemBuilder.createItem(context, collection) + .withTitle("original publication") + .withMetadata("dspace", "entity", "type", publicationEntityType.getLabel()) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, person1, isAuthorOfPublication) + .build(); + + RelationshipBuilder + .createRelationshipBuilder(context, originalPublication, project1, isProjectOfPublication) + .build(); + + RelationshipBuilder + .createRelationshipBuilder(context, originalPublication, orgUnit1, isOrgUnitOfPublication) + .build(); + + ///////////////////////////////////////////////////////// + // verify that the relationships were properly created // + ///////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + ///////////////////////////////////////////// + // create a new version of the publication // + ///////////////////////////////////////////// + + Version newVersion = versioningService.createNewVersion(context, originalPublication); + Item newPublication = newVersion.getItem(); + assertNotSame(originalPublication, newPublication); + + /////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = true) // + /////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + //////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = false) // + //////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + ///////////////////////////////////////////// + // modify relationships on new publication // + ///////////////////////////////////////////// + + Item person2 = ItemBuilder.createItem(context, collection) + .withTitle("person 2") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .build(); + + Item orgUnit2 = ItemBuilder.createItem(context, collection) + .withTitle("org unit 2") + .withMetadata("dspace", "entity", "type", orgUnitEntityType.getLabel()) + .build(); + + // on new item, remove relationship with project 1 + List newProjectRels = relationshipService + .findByItemAndRelationshipType(context, newPublication, isProjectOfPublication); + assertEquals(1, newProjectRels.size()); + relationshipService.delete(context, newProjectRels.get(0)); + + // on new item remove relationship with org unit 1 + List newOrgUnitRels = relationshipService + .findByItemAndRelationshipType(context, newPublication, isOrgUnitOfPublication); + assertEquals(1, newOrgUnitRels.size()); + relationshipService.delete(context, newOrgUnitRels.get(0)); + + RelationshipBuilder.createRelationshipBuilder(context, newPublication, person2, isAuthorOfPublication) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, newPublication, orgUnit2, isOrgUnitOfPublication) + .build(); + + //////////////////////////////////////// + // do item install on new publication // + //////////////////////////////////////// + + WorkspaceItem newPublicationWSI = workspaceItemService.findByItem(context, newPublication); + installItemService.installItem(context, newPublicationWSI); + context.dispatchEvents(); + + /////////////////////////////////////////////////////////////////////// + // verify the relationships of all 7 items (excludeNonLatest = true) // + /////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person2, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, true), + empty() + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), + empty() + ); + + assertThat( + relationshipService.findByItem(context, orgUnit2, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + )) + ); + + //////////////////////////////////////////////////////////////////////// + // verify the relationships of all 7 items (excludeNonLatest = false) // + //////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person2, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit2, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + )) + ); + + ////////////// + // clean up // + ////////////// + + // need to manually delete all relationships to avoid SQL constraint violation exception + List relationships = relationshipService.findAll(context); + for (Relationship relationship : relationships) { + relationshipService.delete(context, relationship); + } } @Test public void test_createNewVersionOfItemOnRightSideOfRelationships() throws Exception { - /////////////////////////////////////////////// - // create a publication with 3 relationships // - /////////////////////////////////////////////// + ////////////////////////////////////////// + // create a person with 3 relationships // + ////////////////////////////////////////// Item publication1 = ItemBuilder.createItem(context, collection) .withTitle("publication 1") @@ -480,11 +918,112 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith )) ); + /////////////////////////////////// + // do item install on new person // + /////////////////////////////////// + + WorkspaceItem newPersonWSI = workspaceItemService.findByItem(context, newPerson); + installItemService.installItem(context, newPersonWSI); + context.dispatchEvents(); + + /////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = true) // + /////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPerson, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, publication1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPerson, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH), + isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH), + isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH) + )) + ); + + //////////////////////////////////////////////////////////////////////// + // verify the relationships of all 5 items (excludeNonLatest = false) // + //////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPerson, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, publication1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY), + isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPerson, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH), + isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH), + isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH) + )) + ); + ////////////// // clean up // ////////////// - versioningService.removeVersion(context, newPerson); + // need to manually delete all relationships to avoid SQL constraint violation exception + List relationships = relationshipService.findAll(context); + for (Relationship relationship : relationships) { + relationshipService.delete(context, relationship); + } } } From f6b72787dee441c0440c46126190d0eac143a6d9 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Thu, 10 Mar 2022 20:00:22 +0100 Subject: [PATCH 0744/1254] 88196: WIP: Test order of plain and virtual metadata (after relationship versioning) --- .../VersioningWithRelationshipsTest.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index a3144a85bc..979d9b8b28 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -16,7 +16,9 @@ import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; import java.util.List; @@ -29,6 +31,7 @@ import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.InstallItemService; +import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.versioning.Version; @@ -48,6 +51,8 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith ContentServiceFactory.getInstance().getWorkspaceItemService(); private final InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + private final ItemService itemService = + ContentServiceFactory.getInstance().getItemService(); protected Community community; protected Collection collection; @@ -1026,4 +1031,87 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } } + @Test + public void test_createNewVersionOfItemAndVerifyMetadataOrder() throws Exception { + ///////////////////////////////////////// + // create a publication with 6 authors // + ///////////////////////////////////////// + + Item originalPublication = ItemBuilder.createItem(context, collection) + .withTitle("original publication") + .withMetadata("dspace", "entity", "type", publicationEntityType.getLabel()) + .build(); + + // author 1 (plain metadata) + itemService.addMetadata(context, originalPublication, "dc", "contributor", "author", null, "author 1 (plain)"); + + // author 2 (virtual) + Item author2 = ItemBuilder.createItem(context, collection) + .withTitle("author 2 (item)") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .withPersonIdentifierFirstName("2 (item)") + .withPersonIdentifierLastName("author") + .build(); + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author2, isAuthorOfPublication) + .build(); + + // author 3 (plain metadata) + itemService.addMetadata(context, originalPublication, "dc", "contributor", "author", null, "author 3 (plain)"); + + // author 4 (virtual) + Item author4 = ItemBuilder.createItem(context, collection) + .withTitle("author 4 (item)") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .withPersonIdentifierFirstName("4 (item)") + .withPersonIdentifierLastName("author") + .build(); + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author4, isAuthorOfPublication) + .build(); + + // author 5 (plain metadata) + itemService.addMetadata(context, originalPublication, "dc", "contributor", "author", null, "author 5 (plain)"); + + // author 6 (virtual) + Item author6 = ItemBuilder.createItem(context, collection) + .withTitle("author 6 (item)") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .withPersonIdentifierFirstName("6 (item)") + .withPersonIdentifierLastName("author") + .build(); + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author6, isAuthorOfPublication) + .build(); + + itemService.update(context, originalPublication); + + //////////////////////////////// + // test dc.contributor.author // + //////////////////////////////// + + List mdvs = itemService.getMetadata( + originalPublication, "dc", "contributor", "author", Item.ANY + ); + + // TODO make sure test setup respects following order + + assertFalse(mdvs.get(0) instanceof RelationshipMetadataValue); + assertEquals("author 1 (plain)", mdvs.get(0).getValue()); + + assertTrue(mdvs.get(1) instanceof RelationshipMetadataValue); + assertEquals("author, 2 (item)", mdvs.get(1).getValue()); + + assertFalse(mdvs.get(2) instanceof RelationshipMetadataValue); + assertEquals("author 3 (plain)", mdvs.get(2).getValue()); + + assertTrue(mdvs.get(3) instanceof RelationshipMetadataValue); + assertEquals("author, 4 (item)", mdvs.get(3).getValue()); + + assertFalse(mdvs.get(4) instanceof RelationshipMetadataValue); + assertEquals("author 5 (plain)", mdvs.get(4).getValue()); + + assertTrue(mdvs.get(5) instanceof RelationshipMetadataValue); + assertEquals("author, 6 (item)", mdvs.get(5).getValue()); + + // TODO finish rest of test + } + } From 938f1bab526ceeb0465fe34daee6fff0f7c6f68b Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Fri, 11 Mar 2022 11:24:04 +0100 Subject: [PATCH 0745/1254] 88196: BUGFIX: Auto-assign place in RelationshipBuilder --- .../src/test/java/org/dspace/builder/RelationshipBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java index 3c9c6800df..5e54b9c8ee 100644 --- a/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/RelationshipBuilder.java @@ -117,7 +117,7 @@ public class RelationshipBuilder extends AbstractBuilder Date: Fri, 11 Mar 2022 15:10:26 +0100 Subject: [PATCH 0746/1254] 88196: WIP: Test place of metadata in new version (implementation needs fix) --- .../versioning/AbstractVersionProvider.java | 15 ++++- .../VersioningWithRelationshipsTest.java | 66 ++++++++++++++----- 2 files changed, 60 insertions(+), 21 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 3633f1949b..329332d315 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java @@ -62,9 +62,18 @@ public abstract class AbstractVersionProvider { continue; } - itemService - .addMetadata(context, itemNew, metadataField, aMd.getLanguage(), aMd.getValue(), aMd.getAuthority(), - aMd.getConfidence()); + itemService.addMetadata( + context, + itemNew, + metadataField.getMetadataSchema().getName(), + metadataField.getElement(), + metadataField.getQualifier(), + aMd.getLanguage(), + aMd.getValue(), + aMd.getAuthority(), + aMd.getConfidence(), + aMd.getPlace() + ); } } diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index 979d9b8b28..34e8176090 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -1081,37 +1081,67 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author6, isAuthorOfPublication) .build(); - itemService.update(context, originalPublication); + //////////////////////////////// + // test dc.contributor.author // + //////////////////////////////// + + List oldMdvs = itemService.getMetadata( + originalPublication, "dc", "contributor", "author", Item.ANY + ); + + assertFalse(oldMdvs.get(0) instanceof RelationshipMetadataValue); + assertEquals("author 1 (plain)", oldMdvs.get(0).getValue()); + + assertTrue(oldMdvs.get(1) instanceof RelationshipMetadataValue); + assertEquals("author, 2 (item)", oldMdvs.get(1).getValue()); + + assertFalse(oldMdvs.get(2) instanceof RelationshipMetadataValue); + assertEquals("author 3 (plain)", oldMdvs.get(2).getValue()); + + assertTrue(oldMdvs.get(3) instanceof RelationshipMetadataValue); + assertEquals("author, 4 (item)", oldMdvs.get(3).getValue()); + + assertFalse(oldMdvs.get(4) instanceof RelationshipMetadataValue); + assertEquals("author 5 (plain)", oldMdvs.get(4).getValue()); + + assertTrue(oldMdvs.get(5) instanceof RelationshipMetadataValue); + assertEquals("author, 6 (item)", oldMdvs.get(5).getValue()); + + /////////////////////////////////////// + // create new version of publication // + /////////////////////////////////////// + + Version newVersion = versioningService.createNewVersion(context, originalPublication); + Item newPublication = newVersion.getItem(); + assertNotSame(originalPublication, newPublication); //////////////////////////////// // test dc.contributor.author // //////////////////////////////// - List mdvs = itemService.getMetadata( - originalPublication, "dc", "contributor", "author", Item.ANY + List newMdvs = itemService.getMetadata( + newPublication, "dc", "contributor", "author", Item.ANY ); - // TODO make sure test setup respects following order + assertFalse(newMdvs.get(0) instanceof RelationshipMetadataValue); + assertEquals("author 1 (plain)", newMdvs.get(0).getValue()); - assertFalse(mdvs.get(0) instanceof RelationshipMetadataValue); - assertEquals("author 1 (plain)", mdvs.get(0).getValue()); + assertTrue(newMdvs.get(1) instanceof RelationshipMetadataValue); + assertEquals("author, 2 (item)", newMdvs.get(1).getValue()); - assertTrue(mdvs.get(1) instanceof RelationshipMetadataValue); - assertEquals("author, 2 (item)", mdvs.get(1).getValue()); + assertFalse(newMdvs.get(2) instanceof RelationshipMetadataValue); + assertEquals("author 3 (plain)", newMdvs.get(2).getValue()); - assertFalse(mdvs.get(2) instanceof RelationshipMetadataValue); - assertEquals("author 3 (plain)", mdvs.get(2).getValue()); + assertTrue(newMdvs.get(3) instanceof RelationshipMetadataValue); + assertEquals("author, 4 (item)", newMdvs.get(3).getValue()); - assertTrue(mdvs.get(3) instanceof RelationshipMetadataValue); - assertEquals("author, 4 (item)", mdvs.get(3).getValue()); + assertFalse(newMdvs.get(4) instanceof RelationshipMetadataValue); + assertEquals("author 5 (plain)", newMdvs.get(4).getValue()); - assertFalse(mdvs.get(4) instanceof RelationshipMetadataValue); - assertEquals("author 5 (plain)", mdvs.get(4).getValue()); + assertTrue(newMdvs.get(5) instanceof RelationshipMetadataValue); + assertEquals("author, 6 (item)", newMdvs.get(5).getValue()); - assertTrue(mdvs.get(5) instanceof RelationshipMetadataValue); - assertEquals("author, 6 (item)", mdvs.get(5).getValue()); - - // TODO finish rest of test + // TODO also test place after removing/adding relationships and metadata } } From df07d1abf11dfe464d491f10548fcd8f39a258fc Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Fri, 11 Mar 2022 16:12:13 +0100 Subject: [PATCH 0747/1254] 88196: Avoid SQL constraint violation in test --- .../content/VersioningWithRelationshipsTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index 34e8176090..accd878c7c 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -1141,7 +1141,15 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertTrue(newMdvs.get(5) instanceof RelationshipMetadataValue); assertEquals("author, 6 (item)", newMdvs.get(5).getValue()); - // TODO also test place after removing/adding relationships and metadata + ////////////// + // clean up // + ////////////// + + // need to manually delete all relationships to avoid SQL constraint violation exception + List relationships = relationshipService.findAll(context); + for (Relationship relationship : relationships) { + relationshipService.delete(context, relationship); + } } } From 23d537f5d9995f39c2b21b48165fd828bf524f77 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Mon, 14 Mar 2022 12:30:00 +0100 Subject: [PATCH 0748/1254] Update code --- dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index 2c295c9e9d..2f9b96d858 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -202,7 +202,7 @@ public class BrowseEngine { // get the table name that we are going to be getting our data from dao.setTable(browseIndex.getTableName()); - dao.setStartsWith(StringUtils.lowerCase(scope.getStartsWith())); + dao.setStartsWith(normalizeJumpToValue(scope.getStartsWith())); // tell the browse query whether we are ascending or descending on the value dao.setAscending(scope.isAscending()); From ca17ebf338f7cf257f39f270fcbd1b55463d8979 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Mon, 14 Mar 2022 16:04:02 +0100 Subject: [PATCH 0749/1254] Update code --- dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java index 2f9b96d858..0969c205cb 100644 --- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -202,7 +203,7 @@ public class BrowseEngine { // get the table name that we are going to be getting our data from dao.setTable(browseIndex.getTableName()); - dao.setStartsWith(normalizeJumpToValue(scope.getStartsWith())); + dao.setStartsWith(StringUtils.lowerCase(scope.getStartsWith())); // tell the browse query whether we are ascending or descending on the value dao.setAscending(scope.isAscending()); From 135c6e6ec079194bbab47d97a6bde023ca00d832 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Mon, 14 Mar 2022 18:16:31 +0100 Subject: [PATCH 0750/1254] 88196: Modify tests to verify that place recalculation does NOT matter --- .../VersioningWithRelationshipsTest.java | 92 ++++++++++++++----- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index accd878c7c..b638c407a2 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -1055,8 +1055,15 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author2, isAuthorOfPublication) .build(); - // author 3 (plain metadata) - itemService.addMetadata(context, originalPublication, "dc", "contributor", "author", null, "author 3 (plain)"); + // author 3 (virtual) + Item author3 = ItemBuilder.createItem(context, collection) + .withTitle("author 3 (item)") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .withPersonIdentifierFirstName("3 (item)") + .withPersonIdentifierLastName("author") + .build(); + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author3, isAuthorOfPublication) + .build(); // author 4 (virtual) Item author4 = ItemBuilder.createItem(context, collection) @@ -1068,17 +1075,40 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author4, isAuthorOfPublication) .build(); - // author 5 (plain metadata) - itemService.addMetadata(context, originalPublication, "dc", "contributor", "author", null, "author 5 (plain)"); - - // author 6 (virtual) - Item author6 = ItemBuilder.createItem(context, collection) - .withTitle("author 6 (item)") + // author 5 (virtual) + Item author5 = ItemBuilder.createItem(context, collection) + .withTitle("author 5 (item)") .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) - .withPersonIdentifierFirstName("6 (item)") + .withPersonIdentifierFirstName("5 (item)") .withPersonIdentifierLastName("author") .build(); - RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author6, isAuthorOfPublication) + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author5, isAuthorOfPublication) + .build(); + + // author 6 (plain metadata) + itemService.addMetadata(context, originalPublication, "dc", "contributor", "author", null, "author 6 (plain)"); + + // author 7 (virtual) + Item author7 = ItemBuilder.createItem(context, collection) + .withTitle("author 7 (item)") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .withPersonIdentifierFirstName("7 (item)") + .withPersonIdentifierLastName("author") + .build(); + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author7, isAuthorOfPublication) + .build(); + + // author 8 (plain metadata) + itemService.addMetadata(context, originalPublication, "dc", "contributor", "author", null, "author 8 (plain)"); + + // author 9 (virtual) + Item author9 = ItemBuilder.createItem(context, collection) + .withTitle("author 9 (item)") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .withPersonIdentifierFirstName("9 (item)") + .withPersonIdentifierLastName("author") + .build(); + RelationshipBuilder.createRelationshipBuilder(context, originalPublication, author9, isAuthorOfPublication) .build(); //////////////////////////////// @@ -1095,17 +1125,26 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertTrue(oldMdvs.get(1) instanceof RelationshipMetadataValue); assertEquals("author, 2 (item)", oldMdvs.get(1).getValue()); - assertFalse(oldMdvs.get(2) instanceof RelationshipMetadataValue); - assertEquals("author 3 (plain)", oldMdvs.get(2).getValue()); + assertTrue(oldMdvs.get(2) instanceof RelationshipMetadataValue); + assertEquals("author, 3 (item)", oldMdvs.get(2).getValue()); assertTrue(oldMdvs.get(3) instanceof RelationshipMetadataValue); assertEquals("author, 4 (item)", oldMdvs.get(3).getValue()); - assertFalse(oldMdvs.get(4) instanceof RelationshipMetadataValue); - assertEquals("author 5 (plain)", oldMdvs.get(4).getValue()); + assertTrue(oldMdvs.get(4) instanceof RelationshipMetadataValue); + assertEquals("author, 5 (item)", oldMdvs.get(4).getValue()); - assertTrue(oldMdvs.get(5) instanceof RelationshipMetadataValue); - assertEquals("author, 6 (item)", oldMdvs.get(5).getValue()); + assertFalse(oldMdvs.get(5) instanceof RelationshipMetadataValue); + assertEquals("author 6 (plain)", oldMdvs.get(5).getValue()); + + assertTrue(oldMdvs.get(6) instanceof RelationshipMetadataValue); + assertEquals("author, 7 (item)", oldMdvs.get(6).getValue()); + + assertFalse(oldMdvs.get(7) instanceof RelationshipMetadataValue); + assertEquals("author 8 (plain)", oldMdvs.get(7).getValue()); + + assertTrue(oldMdvs.get(8) instanceof RelationshipMetadataValue); + assertEquals("author, 9 (item)", oldMdvs.get(8).getValue()); /////////////////////////////////////// // create new version of publication // @@ -1129,17 +1168,26 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertTrue(newMdvs.get(1) instanceof RelationshipMetadataValue); assertEquals("author, 2 (item)", newMdvs.get(1).getValue()); - assertFalse(newMdvs.get(2) instanceof RelationshipMetadataValue); - assertEquals("author 3 (plain)", newMdvs.get(2).getValue()); + assertTrue(newMdvs.get(2) instanceof RelationshipMetadataValue); + assertEquals("author, 3 (item)", newMdvs.get(2).getValue()); assertTrue(newMdvs.get(3) instanceof RelationshipMetadataValue); assertEquals("author, 4 (item)", newMdvs.get(3).getValue()); - assertFalse(newMdvs.get(4) instanceof RelationshipMetadataValue); - assertEquals("author 5 (plain)", newMdvs.get(4).getValue()); + assertTrue(newMdvs.get(4) instanceof RelationshipMetadataValue); + assertEquals("author, 5 (item)", newMdvs.get(4).getValue()); - assertTrue(newMdvs.get(5) instanceof RelationshipMetadataValue); - assertEquals("author, 6 (item)", newMdvs.get(5).getValue()); + assertFalse(newMdvs.get(5) instanceof RelationshipMetadataValue); + assertEquals("author 6 (plain)", newMdvs.get(5).getValue()); + + assertTrue(newMdvs.get(6) instanceof RelationshipMetadataValue); + assertEquals("author, 7 (item)", newMdvs.get(6).getValue()); + + assertFalse(newMdvs.get(7) instanceof RelationshipMetadataValue); + assertEquals("author 8 (plain)", newMdvs.get(7).getValue()); + + assertTrue(newMdvs.get(8) instanceof RelationshipMetadataValue); + assertEquals("author, 9 (item)", newMdvs.get(8).getValue()); ////////////// // clean up // From 3fc4131cb2c25a97dc3263386be39418688d63eb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 15 Mar 2022 10:55:10 -0500 Subject: [PATCH 0751/1254] Fix compare by returning 0 when bitstreams are "equal" in size & priority --- .../java/org/dspace/app/util/GoogleBitstreamComparator.java | 4 +++- .../org/dspace/app/util/GoogleBitstreamComparatorTest.java | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/GoogleBitstreamComparator.java b/dspace-api/src/main/java/org/dspace/app/util/GoogleBitstreamComparator.java index add98af96f..ae6ba7e83f 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/GoogleBitstreamComparator.java +++ b/dspace-api/src/main/java/org/dspace/app/util/GoogleBitstreamComparator.java @@ -86,8 +86,10 @@ public class GoogleBitstreamComparator implements Comparator { if (priority1 > priority2) { return 1; } else if (priority1 == priority2) { - if (b1.getSizeBytes() <= b2.getSizeBytes()) { + if (b1.getSizeBytes() < b2.getSizeBytes()) { return 1; + } else if (b1.getSizeBytes() == b2.getSizeBytes()) { + return 0; } else { return -1; } diff --git a/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java b/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java index 84e776b983..78142c9258 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/GoogleBitstreamComparatorTest.java @@ -164,6 +164,12 @@ public class GoogleBitstreamComparatorTest extends AbstractUnitTest { toSort.get(1).getName()); assertEquals("Bitstreams have same size and type, so order should remain unchanged", "bitstream3", toSort.get(2).getName()); + + // Also, verify all bitstreams are considered equal (comparison returns 0) + GoogleBitstreamComparator comparator = new GoogleBitstreamComparator(context, settings); + assertEquals(0, comparator.compare(bitstream1, bitstream2)); + assertEquals(0, comparator.compare(bitstream2, bitstream3)); + assertEquals(0, comparator.compare(bitstream3, bitstream1)); } /** From 585f7c67a91c9cc8cca112987bf1a49eab28a352 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 15 Mar 2022 13:39:38 -0400 Subject: [PATCH 0752/1254] Move helpdesk email settings up with other emails, as requested. [DS-4118] #2474 --- dspace/config/dspace.cfg | 7 ++++--- dspace/config/local.cfg.EXAMPLE | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 63a37a11bb..ade2176f44 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -149,6 +149,10 @@ feedback.recipient = dspace-help@myu.edu mail.admin = dspace-help@myu.edu mail.admin.name = DSpace Administrator +# Helpdesk E-mail +mail.helpdesk = ${mail.admin} +mail.helpdesk.name = Help Desk + # Recipient for server errors and alerts (defaults to mail.admin) alert.recipient = ${mail.admin} @@ -1523,9 +1527,6 @@ log.report.dir = ${dspace.dir}/log # logged - Login is mandatory to request an item # empty/commented out - request-copy not allowed request.item.type = all -# Helpdesk E-mail -mail.helpdesk = ${mail.admin} -mail.helpdesk.name = Help Desk # Should all Request Copy emails go to the helpdesk instead of the item submitter? request.item.helpdesk.override = false diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index f74541bca9..e247d5beca 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -128,6 +128,10 @@ db.schema = public # General site administration (Webmaster) e-mail #mail.admin = dspace-help@myu.edu +# Helpdesk E-mail +#mail.helpdesk = ${mail.admin} +#mail.helpdesk.name = Help Desk + # Recipient for server errors and alerts (defaults to mail.admin) #alert.recipient = ${mail.admin} From b316cb1cec2e8879c1ad56f91196b37fe98382d2 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 15 Mar 2022 14:03:51 -0400 Subject: [PATCH 0753/1254] Remove all traces of disk cache to eliminate cache directory collisions. #8099 --- dspace/config/hibernate-ehcache-config.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dspace/config/hibernate-ehcache-config.xml b/dspace/config/hibernate-ehcache-config.xml index bd2f28f482..0900ce0b50 100644 --- a/dspace/config/hibernate-ehcache-config.xml +++ b/dspace/config/hibernate-ehcache-config.xml @@ -18,8 +18,6 @@ - - @@ -131,7 +128,6 @@ 4000 - @@ -144,7 +140,6 @@ 2000 - From 77b00cea7e84ff9ff7f53277fb1c97ea936cf205 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 16 Mar 2022 09:17:07 -0400 Subject: [PATCH 0754/1254] Rename Hibernate caches as suggested by Hibernate. #8213 --- dspace/config/hibernate-ehcache-config.xml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/dspace/config/hibernate-ehcache-config.xml b/dspace/config/hibernate-ehcache-config.xml index 0900ce0b50..e2edf67b60 100644 --- a/dspace/config/hibernate-ehcache-config.xml +++ b/dspace/config/hibernate-ehcache-config.xml @@ -31,20 +31,22 @@ - - + + 6000 - - + + 600 From 9288a18c4ff4897a3f9f55a19bd8b29b16377b9f Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 16 Mar 2022 15:21:14 +0100 Subject: [PATCH 0755/1254] 88061: Verify modified relationships on new version of item before item install --- .../VersioningWithRelationshipsTest.java | 210 ++++++++++-------- 1 file changed, 121 insertions(+), 89 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index b638c407a2..317def4d07 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -506,95 +506,6 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith Item newPublication = newVersion.getItem(); assertNotSame(originalPublication, newPublication); - /////////////////////////////////////////////////////////////////////// - // verify the relationships of all 5 items (excludeNonLatest = true) // - /////////////////////////////////////////////////////////////////////// - - assertThat( - relationshipService.findByItem(context, originalPublication, -1, -1, false, true), - containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) - )) - ); - - assertThat( - relationshipService.findByItem(context, person1, -1, -1, false, true), - containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) - )) - ); - - assertThat( - relationshipService.findByItem(context, project1, -1, -1, false, true), - containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) - )) - ); - - assertThat( - relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), - containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) - )) - ); - - assertThat( - relationshipService.findByItem(context, newPublication, -1, -1, false, true), - containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) - )) - ); - - //////////////////////////////////////////////////////////////////////// - // verify the relationships of all 5 items (excludeNonLatest = false) // - //////////////////////////////////////////////////////////////////////// - - assertThat( - relationshipService.findByItem(context, originalPublication, -1, -1, false, false), - containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) - )) - ); - - assertThat( - relationshipService.findByItem(context, person1, -1, -1, false, false), - containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY) - )) - ); - - assertThat( - relationshipService.findByItem(context, project1, -1, -1, false, false), - containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY) - )) - ); - - assertThat( - relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), - containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) - )) - ); - - assertThat( - relationshipService.findByItem(context, newPublication, -1, -1, false, false), - containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) - )) - ); - ///////////////////////////////////////////// // modify relationships on new publication // ///////////////////////////////////////////// @@ -627,6 +538,127 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith RelationshipBuilder.createRelationshipBuilder(context, newPublication, orgUnit2, isOrgUnitOfPublication) .build(); + /////////////////////////////////////////////////////////////////////// + // verify the relationships of all 7 items (excludeNonLatest = true) // + /////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person2, -1, -1, false, true), + containsInAnyOrder(List.of( + // NOTE: BOTH because new relationship + isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit2, -1, -1, false, true), + containsInAnyOrder(List.of( + // NOTE: BOTH because new relationship + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, true), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + // NOTE: BOTH because new relationship + isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + )) + ); + + //////////////////////////////////////////////////////////////////////// + // verify the relationships of all 7 items (excludeNonLatest = false) // + //////////////////////////////////////////////////////////////////////// + + assertThat( + relationshipService.findByItem(context, originalPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, person1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY) + )) + ); + + assertThat( + relationshipService.findByItem(context, person2, -1, -1, false, false), + containsInAnyOrder(List.of( + // NOTE: BOTH because new relationship + isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, project1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, orgUnit2, -1, -1, false, false), + containsInAnyOrder(List.of( + // NOTE: BOTH because new relationship + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + )) + ); + + assertThat( + relationshipService.findByItem(context, newPublication, -1, -1, false, false), + containsInAnyOrder(List.of( + isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + // NOTE: BOTH because new relationship + isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH), + isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + )) + ); + //////////////////////////////////////// // do item install on new publication // //////////////////////////////////////// From 7e0becf666c3ca9d04a63837895cd98cb51f4ec5 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 16 Mar 2022 11:50:22 -0400 Subject: [PATCH 0756/1254] Replace --all with opposite --first, default off, as requested. [DS-4478] #3312 --- .../java/org/dspace/app/util/Configuration.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/Configuration.java b/dspace-api/src/main/java/org/dspace/app/util/Configuration.java index 3a38daf7e7..e4a59eeb4d 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/Configuration.java +++ b/dspace-api/src/main/java/org/dspace/app/util/Configuration.java @@ -37,6 +37,7 @@ public class Configuration { *
  • {@code --property name} prints the value of the DSpace configuration * property {@code name} to the standard output.
  • *
  • {@code --raw} suppresses parameter substitution in the output.
  • + *
  • {@code --first} print only the first of multiple values.
  • *
  • {@code --help} describes these options.
  • * * If the property does not exist, nothing is written. @@ -51,8 +52,8 @@ public class Configuration { "optional name of the module in which 'property' exists"); options.addOption("r", "raw", false, "do not do property substitution on the value"); - options.addOption("a", "all", false, - "display all values of an array property"); + options.addOption("f", "first", false, + "display only the first value of an array property"); options.addOption("?", "Get help"); options.addOption("h", "help", false, "Get help"); @@ -103,8 +104,8 @@ public class Configuration { if (rawValue.getClass().isArray()) { for (Object value : (Object[]) rawValue) { System.out.println(value.toString()); - if (!cmd.hasOption('a')) { - break; // Unless --all print only one value + if (cmd.hasOption('f')) { + break; // If --first print only one value } } } else { // Not an array @@ -115,8 +116,8 @@ public class Configuration { String[] values = cfg.getArrayProperty(propName); for (String value : values) { System.out.println(value); - if (!cmd.hasOption('a')) { - break; // Unless --all print only one value + if (cmd.hasOption('f')) { + break; // If --first print only one value } } } From c0aee566a0cd35518a5de5aab38a3b5ea9f5134c Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 16 Mar 2022 12:08:10 -0400 Subject: [PATCH 0757/1254] Oops, fix the tests to understand the new options. #3312 --- .../src/test/java/org/dspace/app/util/ConfigurationIT.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java b/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java index 8f18c9754e..20e48f84de 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java +++ b/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java @@ -94,7 +94,6 @@ public class ConfigurationIT public void testMainAllSingle() { String[] argv; argv = new String[] { - "--all", "--property", SINGLE_PROPERTY }; expectedSystemExit.expectSystemExitWithStatus(0); @@ -123,7 +122,6 @@ public class ConfigurationIT public void testMainAllArray() { String[] argv; argv = new String[] { - "--all", "--property", ARRAY_PROPERTY }; expectedSystemExit.expectSystemExitWithStatus(0); @@ -153,7 +151,6 @@ public class ConfigurationIT public void testMainAllSubstitution() { String[] argv; argv = new String[] { - "--all", "--property", PLACEHOLDER_PROPERTY }; expectedSystemExit.expectSystemExitWithStatus(0); @@ -184,7 +181,6 @@ public class ConfigurationIT // Can it handle a raw property (with substitution placeholders)? String[] argv; argv = new String[] { - "--all", "--property", PLACEHOLDER_PROPERTY, "--raw" }; @@ -215,7 +211,6 @@ public class ConfigurationIT // Can it handle an undefined property? String[] argv; argv = new String[] { - "--all", "--property", MISSING_PROPERTY }; expectedSystemExit.expectSystemExitWithStatus(0); From f3ff9af3e2830575e02b5734569e360b3d7abbe3 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 16 Mar 2022 17:58:59 +0100 Subject: [PATCH 0758/1254] 88061: Clarify VersioningConsumer with extra comments --- .../dspace/versioning/VersioningConsumer.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java index b9489d1b22..b186daad8a 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java +++ b/dspace-api/src/main/java/org/dspace/versioning/VersioningConsumer.java @@ -143,6 +143,31 @@ public class VersioningConsumer implements Consumer { )); } + /** + * Update {@link Relationship#latestVersionStatus} of the relationships of both the old version and the new version + * of the item. + * + * This method will first locate all relationships that are eligible for an update, + * then it will try to match each of those relationships on the old version of given item + * with a relationship on the new version. + * + * One of the following scenarios will happen: + * - if a match is found, then the "latest" status on the side of given item is transferred from + * the old relationship to the new relationship. This implies that on the page of the third-party item, + * the old version of given item will NOT be shown anymore and the new version of given item will appear. + * Both versions of the given item still show the third-party item on their pages. + * - if a relationship only exists on the new version of given item, then this method does nothing. + * The status of those relationships should already have been set to "latest" on both sides during relationship + * creation. + * - if a relationship only exists on the old version of given item, then we assume that the relationship is no + * longer relevant to / has been removed from the new version of the item. The "latest" status is removed from + * the side of the given item. This implies that on the page of the third-party item, + * the relationship with given item will no longer be listed. The old version of given item still lists + * the third-party item and the new version doesn't. + * @param ctx the DSpace context. + * @param latestItem the new version of the item. + * @param previousItem the old version of the item. + */ protected void updateRelationships(Context ctx, Item latestItem, Item previousItem) { // check that the entity types of both items match if (!doEntityTypesMatch(latestItem, previousItem)) { @@ -172,6 +197,9 @@ public class VersioningConsumer implements Consumer { continue; } + // NOTE: no need to loop through latestItemRelationships, because if no match can be found + // (meaning a relationship is only present on the new version of the item), then it's + // a newly added relationship and its status should have been set to BOTH during creation. for (Relationship previousItemRelationship : previousItemRelationships) { // determine on which side of the relationship the latest and previous item should be boolean isLeft = previousItem.equals(previousItemRelationship.getLeftItem()); @@ -195,12 +223,16 @@ public class VersioningConsumer implements Consumer { Relationship latestItemRelationship = getMatchingRelationship(latestItem, isLeft, previousItemRelationship, latestItemRelationships); - // for sure set the previous item to non-latest - // NOTE: if no matching relationship exists, this relationship will be considered deleted - // when viewed from the other item + // Set the previous version of the item to non-latest. This implies that the previous version + // of the item will not be shown anymore on the page of the third-party item. That makes sense, + // because either the relationship has been deleted from the new version of the item (no match), + // or the matching relationship (linked to new version) will receive "latest" status in the next step. updateLatestVersionStatus(previousItemRelationship, isLeft, false); - // set the new item to latest if the relevant relationship exists + // Set the new version of the item to latest if the relevant relationship exists (match found). + // This implies that the new version of the item will appear on the page of the third-party item. + // The old version of the item will not appear anymore on the page of the third-party item, + // see previous step. if (latestItemRelationship != null) { updateLatestVersionStatus(latestItemRelationship, isLeft, true); } From 1fcff8900704c628f41d92633358c9804e397a9b Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Fri, 18 Mar 2022 01:32:56 +0100 Subject: [PATCH 0759/1254] 88595: Set versioning.block.entity = false --- dspace/config/modules/versioning.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/modules/versioning.cfg b/dspace/config/modules/versioning.cfg index a345294242..2d2ba9450b 100644 --- a/dspace/config/modules/versioning.cfg +++ b/dspace/config/modules/versioning.cfg @@ -23,4 +23,4 @@ versioning.item.history.include.submitter=false # The property versioning.block.entity is used to disable versioning # for items with EntityType, the default value is true if it unset. -# versioning.block.entity = true \ No newline at end of file +versioning.block.entity=false From 46d9ba91bc93b3451b6f9a874b4cdb956a39a9dc Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Mon, 21 Mar 2022 10:26:43 +0100 Subject: [PATCH 0760/1254] 88599: Test metadata and relationship place when creating new version of item --- .../VersioningWithRelationshipsTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index 317def4d07..272d2ccb2b 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -1150,33 +1150,86 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith List oldMdvs = itemService.getMetadata( originalPublication, "dc", "contributor", "author", Item.ANY ); + assertEquals(9, oldMdvs.size()); assertFalse(oldMdvs.get(0) instanceof RelationshipMetadataValue); assertEquals("author 1 (plain)", oldMdvs.get(0).getValue()); + assertEquals(0, oldMdvs.get(0).getPlace()); assertTrue(oldMdvs.get(1) instanceof RelationshipMetadataValue); assertEquals("author, 2 (item)", oldMdvs.get(1).getValue()); + assertEquals(1, oldMdvs.get(1).getPlace()); assertTrue(oldMdvs.get(2) instanceof RelationshipMetadataValue); assertEquals("author, 3 (item)", oldMdvs.get(2).getValue()); + assertEquals(2, oldMdvs.get(2).getPlace()); assertTrue(oldMdvs.get(3) instanceof RelationshipMetadataValue); assertEquals("author, 4 (item)", oldMdvs.get(3).getValue()); + assertEquals(3, oldMdvs.get(3).getPlace()); assertTrue(oldMdvs.get(4) instanceof RelationshipMetadataValue); assertEquals("author, 5 (item)", oldMdvs.get(4).getValue()); + assertEquals(4, oldMdvs.get(4).getPlace()); assertFalse(oldMdvs.get(5) instanceof RelationshipMetadataValue); assertEquals("author 6 (plain)", oldMdvs.get(5).getValue()); + assertEquals(5, oldMdvs.get(5).getPlace()); assertTrue(oldMdvs.get(6) instanceof RelationshipMetadataValue); assertEquals("author, 7 (item)", oldMdvs.get(6).getValue()); + assertEquals(6, oldMdvs.get(6).getPlace()); assertFalse(oldMdvs.get(7) instanceof RelationshipMetadataValue); assertEquals("author 8 (plain)", oldMdvs.get(7).getValue()); + assertEquals(7, oldMdvs.get(7).getPlace()); assertTrue(oldMdvs.get(8) instanceof RelationshipMetadataValue); assertEquals("author, 9 (item)", oldMdvs.get(8).getValue()); + assertEquals(8, oldMdvs.get(8).getPlace()); + + ///////////////////////////////////////////// + // test relationship isAuthorOfPublication // + ///////////////////////////////////////////// + + List oldRelationships = relationshipService.findByItem(context, originalPublication); + assertEquals(6, oldRelationships.size()); + + assertEquals(originalPublication, oldRelationships.get(0).getLeftItem()); + assertEquals(isAuthorOfPublication, oldRelationships.get(0).getRelationshipType()); + assertEquals(author2, oldRelationships.get(0).getRightItem()); + assertEquals(1, oldRelationships.get(0).getLeftPlace()); + assertEquals(0, oldRelationships.get(0).getRightPlace()); + + assertEquals(originalPublication, oldRelationships.get(1).getLeftItem()); + assertEquals(isAuthorOfPublication, oldRelationships.get(1).getRelationshipType()); + assertEquals(author3, oldRelationships.get(1).getRightItem()); + assertEquals(2, oldRelationships.get(1).getLeftPlace()); + assertEquals(0, oldRelationships.get(1).getRightPlace()); + + assertEquals(originalPublication, oldRelationships.get(2).getLeftItem()); + assertEquals(isAuthorOfPublication, oldRelationships.get(2).getRelationshipType()); + assertEquals(author4, oldRelationships.get(2).getRightItem()); + assertEquals(3, oldRelationships.get(2).getLeftPlace()); + assertEquals(0, oldRelationships.get(2).getRightPlace()); + + assertEquals(originalPublication, oldRelationships.get(3).getLeftItem()); + assertEquals(isAuthorOfPublication, oldRelationships.get(3).getRelationshipType()); + assertEquals(author5, oldRelationships.get(3).getRightItem()); + assertEquals(4, oldRelationships.get(3).getLeftPlace()); + assertEquals(0, oldRelationships.get(3).getRightPlace()); + + assertEquals(originalPublication, oldRelationships.get(4).getLeftItem()); + assertEquals(isAuthorOfPublication, oldRelationships.get(4).getRelationshipType()); + assertEquals(author7, oldRelationships.get(4).getRightItem()); + assertEquals(6, oldRelationships.get(4).getLeftPlace()); + assertEquals(0, oldRelationships.get(4).getRightPlace()); + + assertEquals(originalPublication, oldRelationships.get(5).getLeftItem()); + assertEquals(isAuthorOfPublication, oldRelationships.get(5).getRelationshipType()); + assertEquals(author9, oldRelationships.get(5).getRightItem()); + assertEquals(8, oldRelationships.get(5).getLeftPlace()); + assertEquals(0, oldRelationships.get(5).getRightPlace()); /////////////////////////////////////// // create new version of publication // @@ -1193,33 +1246,86 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith List newMdvs = itemService.getMetadata( newPublication, "dc", "contributor", "author", Item.ANY ); + assertEquals(9, newMdvs.size()); assertFalse(newMdvs.get(0) instanceof RelationshipMetadataValue); assertEquals("author 1 (plain)", newMdvs.get(0).getValue()); + assertEquals(0, newMdvs.get(0).getPlace()); assertTrue(newMdvs.get(1) instanceof RelationshipMetadataValue); assertEquals("author, 2 (item)", newMdvs.get(1).getValue()); + assertEquals(1, newMdvs.get(1).getPlace()); assertTrue(newMdvs.get(2) instanceof RelationshipMetadataValue); assertEquals("author, 3 (item)", newMdvs.get(2).getValue()); + assertEquals(2, newMdvs.get(2).getPlace()); assertTrue(newMdvs.get(3) instanceof RelationshipMetadataValue); assertEquals("author, 4 (item)", newMdvs.get(3).getValue()); + assertEquals(3, newMdvs.get(3).getPlace()); assertTrue(newMdvs.get(4) instanceof RelationshipMetadataValue); assertEquals("author, 5 (item)", newMdvs.get(4).getValue()); + assertEquals(4, newMdvs.get(4).getPlace()); assertFalse(newMdvs.get(5) instanceof RelationshipMetadataValue); assertEquals("author 6 (plain)", newMdvs.get(5).getValue()); + assertEquals(5, newMdvs.get(5).getPlace()); assertTrue(newMdvs.get(6) instanceof RelationshipMetadataValue); assertEquals("author, 7 (item)", newMdvs.get(6).getValue()); + assertEquals(6, newMdvs.get(6).getPlace()); assertFalse(newMdvs.get(7) instanceof RelationshipMetadataValue); assertEquals("author 8 (plain)", newMdvs.get(7).getValue()); + assertEquals(7, newMdvs.get(7).getPlace()); assertTrue(newMdvs.get(8) instanceof RelationshipMetadataValue); assertEquals("author, 9 (item)", newMdvs.get(8).getValue()); + assertEquals(8, newMdvs.get(8).getPlace()); + + ///////////////////////////////////////////// + // test relationship isAuthorOfPublication // + ///////////////////////////////////////////// + + List newRelationships = relationshipService.findByItem(context, newPublication); + assertEquals(6, newRelationships.size()); + + assertEquals(newPublication, newRelationships.get(0).getLeftItem()); + assertEquals(isAuthorOfPublication, newRelationships.get(0).getRelationshipType()); + assertEquals(author2, newRelationships.get(0).getRightItem()); + assertEquals(1, newRelationships.get(0).getLeftPlace()); + assertEquals(0, newRelationships.get(0).getRightPlace()); + + assertEquals(newPublication, newRelationships.get(1).getLeftItem()); + assertEquals(isAuthorOfPublication, newRelationships.get(1).getRelationshipType()); + assertEquals(author3, newRelationships.get(1).getRightItem()); + assertEquals(2, newRelationships.get(1).getLeftPlace()); + assertEquals(0, newRelationships.get(1).getRightPlace()); + + assertEquals(newPublication, newRelationships.get(2).getLeftItem()); + assertEquals(isAuthorOfPublication, newRelationships.get(2).getRelationshipType()); + assertEquals(author4, newRelationships.get(2).getRightItem()); + assertEquals(3, newRelationships.get(2).getLeftPlace()); + assertEquals(0, newRelationships.get(2).getRightPlace()); + + assertEquals(newPublication, newRelationships.get(3).getLeftItem()); + assertEquals(isAuthorOfPublication, newRelationships.get(3).getRelationshipType()); + assertEquals(author5, newRelationships.get(3).getRightItem()); + assertEquals(4, newRelationships.get(3).getLeftPlace()); + assertEquals(0, newRelationships.get(3).getRightPlace()); + + assertEquals(newPublication, newRelationships.get(4).getLeftItem()); + assertEquals(isAuthorOfPublication, newRelationships.get(4).getRelationshipType()); + assertEquals(author7, newRelationships.get(4).getRightItem()); + assertEquals(6, newRelationships.get(4).getLeftPlace()); + assertEquals(0, newRelationships.get(4).getRightPlace()); + + assertEquals(newPublication, newRelationships.get(5).getLeftItem()); + assertEquals(isAuthorOfPublication, newRelationships.get(5).getRelationshipType()); + assertEquals(author9, newRelationships.get(5).getRightItem()); + assertEquals(8, newRelationships.get(5).getLeftPlace()); + assertEquals(0, newRelationships.get(5).getRightPlace()); ////////////// // clean up // From bdf83c73a685eeba5aa4e6f780d2bf695cab9c07 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 21 Mar 2022 09:54:07 -0400 Subject: [PATCH 0761/1254] Add tests for use of '--first'. #3312 --- .../org/dspace/app/util/ConfigurationIT.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java b/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java index 20e48f84de..388b467e97 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java +++ b/dspace-api/src/test/java/org/dspace/app/util/ConfigurationIT.java @@ -12,6 +12,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.collection.IsArrayContainingInAnyOrder.arrayContainingInAnyOrder; +import static org.junit.Assert.assertEquals; import org.dspace.AbstractDSpaceTest; import org.dspace.services.ConfigurationService; @@ -224,4 +225,44 @@ public class ConfigurationIT systemOutRule.enableLog(); Configuration.main(argv); } + + /** + * Test fetching only the first value of an array property. + */ + @Test + public void testMainFirstArray() { + String[] argv = new String[] { + "--property", ARRAY_PROPERTY, + "--first" + }; + expectedSystemExit.expectSystemExitWithStatus(0); + expectedSystemExit.checkAssertionAfterwards(() -> { + String outputs = systemOutRule.getLogWithNormalizedLineSeparator(); + String[] output = outputs.split("\n"); + assertThat(output, arrayWithSize(1)); + assertEquals("--first should return first value", output[0], ARRAY_VALUE[0]); + }); + systemOutRule.enableLog(); + Configuration.main(argv); + } + + /** + * Test fetching a single-valued property using {@code --first} + */ + @Test + public void testMainFirstSingle() { + String[] argv = new String[] { + "--property", SINGLE_PROPERTY, + "--first" + }; + expectedSystemExit.expectSystemExitWithStatus(0); + expectedSystemExit.checkAssertionAfterwards(() -> { + String outputs = systemOutRule.getLogWithNormalizedLineSeparator(); + String[] output = outputs.split("\n"); + assertThat(output, arrayWithSize(1)); + assertEquals("--first should return only value", output[0], SINGLE_VALUE); + }); + systemOutRule.enableLog(); + Configuration.main(argv); + } } From 85bbcf3dc8fd6c0d4243bf32e1e25e042db87eb3 Mon Sep 17 00:00:00 2001 From: Samuel Date: Fri, 18 Mar 2022 17:44:35 +0100 Subject: [PATCH 0762/1254] taskid 88773 #8023 Bitstream download filename lose non-latin characters --- .../rest/utils/HttpHeadersInitializer.java | 4 +- .../app/rest/BitstreamRestControllerIT.java | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index 22ae8ad3d9..ffe2acc2e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.utils; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; +import static javax.mail.internet.MimeUtility.encodeText; import java.io.IOException; import java.util.Arrays; @@ -163,7 +164,8 @@ public class HttpHeadersInitializer { } httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, - disposition, fileName))); + disposition, + encodeText(fileName)))); log.debug("Content-Disposition : {}", disposition); // Content phase diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index f07aae876f..efb7d1776d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static java.util.UUID.randomUUID; +import static javax.mail.internet.MimeUtility.encodeText; import static org.apache.commons.codec.CharEncoding.UTF_8; import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.io.IOUtils.toInputStream; @@ -293,6 +294,53 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest checkNumberOfStatsRecords(bitstream, 0); } + @Test + public void testBitstreamName() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collection + + parentCommunity = CommunityBuilder + .createCommunity(context) + .build(); + + Collection collection = CollectionBuilder + .createCollection(context, parentCommunity) + .build(); + + //2. A public item with a bitstream + + String bitstreamContent = "0123456789"; + String bitstreamName = "ภาษาไทย"; + + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + + Item item = ItemBuilder + .createItem(context, collection) + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, item, is) + .withName(bitstreamName) + .build(); + } + + context.restoreAuthSystemState(); + + //** WHEN ** + //We download the bitstream + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + //** THEN ** + .andExpect(status().isOk()) + //We expect the content disposition to have the encoded bitstream name + .andExpect(header().string( + "Content-Disposition", + "attachment;filename=\"" + encodeText(bitstreamName) + "\"" + )); + } + @Test public void testBitstreamNotFound() throws Exception { getClient().perform(get("/api/core/bitstreams/" + UUID.randomUUID() + "/content")) From 0d148abf1169590831dcba2c15d805541be080f7 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Tue, 22 Mar 2022 11:22:32 +0100 Subject: [PATCH 0763/1254] 88629: Prove that ItemServiceImpl#rawDelete is broken --- .../content/service/ItemServiceTest.java | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 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 c66a6eed76..dbed948737 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 @@ -9,6 +9,7 @@ package org.dspace.content.service; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.sql.SQLException; @@ -17,18 +18,28 @@ import java.util.List; import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; -import org.dspace.AbstractUnitTest; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.RelationshipBuilder; +import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.EntityType; import org.dspace.content.Item; import org.dspace.content.MetadataValue; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.versioning.Version; +import org.dspace.versioning.factory.VersionServiceFactory; +import org.dspace.versioning.service.VersioningService; import org.junit.Before; import org.junit.Test; -public class ItemServiceTest extends AbstractUnitTest { +public class ItemServiceTest extends AbstractIntegrationTestWithDatabase { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemServiceTest.class); protected RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); @@ -41,6 +52,7 @@ public class ItemServiceTest extends AbstractUnitTest { protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); + protected VersioningService versioningService = VersionServiceFactory.getInstance().getVersionService(); Community community; Collection col; @@ -57,8 +69,8 @@ public class ItemServiceTest extends AbstractUnitTest { */ @Before @Override - public void init() { - super.init(); + public void setUp() throws Exception { + super.setUp(); try { context.turnOffAuthorisationSystem(); community = communityService.create(null, context); @@ -190,6 +202,54 @@ public class ItemServiceTest extends AbstractUnitTest { assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3)); } + @Test + public void testDeleteItemWithMultipleVersions() throws Exception { + context.turnOffAuthorisationSystem(); + + EntityType publicationEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication") + .build(); + + EntityType personEntityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Person") + .build(); + + RelationshipType isAuthorOfPublication = RelationshipTypeBuilder.createRelationshipTypeBuilder( + context, publicationEntityType, personEntityType, "isAuthorOfPublication", "isPublicationOfAuthor", + null, null, null, null + ) + .withCopyToLeft(false) + .withCopyToRight(false) + .build(); + + Item publication1 = ItemBuilder.createItem(context, col) + .withTitle("publication 1") + .withEntityType("Publication") + .build(); + + Item person1 = ItemBuilder.createItem(context, col) + .withTitle("person 2") + .withEntityType("Person") + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, person1, isAuthorOfPublication); + + // create a new version, which results in a non-latest relationship attached person 1. + Version newVersion = versioningService.createNewVersion(context, publication1); + Item newPublication1 = newVersion.getItem(); + WorkspaceItem newPublication1WSI = workspaceItemService.findByItem(context, newPublication1); + installItemService.installItem(context, newPublication1WSI); + context.dispatchEvents(); + + // verify person1 has a non-latest relationship, which should also be removed + List relationships1 = relationshipService.findByItem(context, person1, -1, -1, false, true); + assertEquals(1, relationships1.size()); + List relationships2 = relationshipService.findByItem(context, person1, -1, -1, false, false); + assertEquals(2, relationships2.size()); + + itemService.delete(context, person1); + + context.restoreAuthSystemState(); + } + private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value, String authority, int place, MetadataValue metadataValue) { assertThat(metadataValue.getValue(), equalTo(value)); From d55935c19748253f68e2c6b08a4da9679eade1f7 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Tue, 22 Mar 2022 11:25:15 +0100 Subject: [PATCH 0764/1254] 88629: Fix ItemServiceImpl#rawDelete --- .../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 96dac1a4df..c1848a1ebf 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -724,7 +724,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It + item.getID())); // Remove relationships - for (Relationship relationship : relationshipService.findByItem(context, item)) { + for (Relationship relationship : relationshipService.findByItem(context, item, -1, -1, false, false)) { relationshipService.forceDelete(context, relationship, false, false); } From ae25c67064f9c4a841c355e81b228c71050cc6f3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 15 Mar 2022 09:47:03 -0500 Subject: [PATCH 0765/1254] Update to JDOM2. Also replaced deprecated XPath with new XPathFactory --- dspace-api/pom.xml | 2 +- .../org/dspace/administer/StructBuilder.java | 10 +-- .../dspace/app/launcher/CommandRunner.java | 2 +- .../dspace/app/launcher/ScriptLauncher.java | 6 +- .../org/dspace/app/util/GoogleMetadata.java | 2 +- .../app/util/OpenSearchServiceImpl.java | 16 ++-- .../content/crosswalk/AIPDIMCrosswalk.java | 4 +- .../content/crosswalk/AIPTechMDCrosswalk.java | 4 +- .../crosswalk/DIMDisseminationCrosswalk.java | 4 +- .../crosswalk/DIMIngestionCrosswalk.java | 4 +- .../crosswalk/DisseminationCrosswalk.java | 4 +- .../content/crosswalk/IngestionCrosswalk.java | 2 +- .../crosswalk/METSDisseminationCrosswalk.java | 10 +-- .../crosswalk/METSRightsCrosswalk.java | 4 +- .../crosswalk/MODSDisseminationCrosswalk.java | 84 ++++++++--------- .../crosswalk/NullIngestionCrosswalk.java | 6 +- .../crosswalk/OAIDCIngestionCrosswalk.java | 4 +- .../crosswalk/OREDisseminationCrosswalk.java | 4 +- .../crosswalk/OREIngestionCrosswalk.java | 51 +++++------ .../content/crosswalk/PREMISCrosswalk.java | 4 +- .../ParameterizedDisseminationCrosswalk.java | 2 +- .../content/crosswalk/QDCCrosswalk.java | 11 +-- .../content/crosswalk/RoleCrosswalk.java | 12 +-- .../SimpleDCDisseminationCrosswalk.java | 5 +- .../XHTMLHeadDisseminationCrosswalk.java | 7 +- .../content/crosswalk/XSLTCrosswalk.java | 2 +- .../crosswalk/XSLTDisseminationCrosswalk.java | 26 ++++-- .../crosswalk/XSLTIngestionCrosswalk.java | 23 +++-- .../packager/AbstractMETSDisseminator.java | 8 +- .../packager/AbstractMETSIngester.java | 2 +- .../content/packager/DSpaceAIPIngester.java | 2 +- .../content/packager/DSpaceMETSIngester.java | 2 +- .../dspace/content/packager/METSManifest.java | 90 +++++++++---------- .../content/packager/RoleDisseminator.java | 2 +- .../HarvestedCollectionServiceImpl.java | 8 +- .../java/org/dspace/harvest/OAIHarvester.java | 10 +-- .../dspace/identifier/DataCiteXMLCreator.java | 4 +- .../identifier/doi/DataCiteConnector.java | 14 +-- .../license/CCLicenseConnectorService.java | 2 +- .../CCLicenseConnectorServiceImpl.java | 16 ++-- .../license/CreativeCommonsServiceImpl.java | 4 +- .../service/CreativeCommonsService.java | 2 +- .../org/dspace/testing/PubMedToImport.java | 8 +- .../AbstractIntegrationTestWithDatabase.java | 2 +- .../org/dspace/app/packager/PackagerIT.java | 2 +- .../content/crosswalk/QDCCrosswalkTest.java | 2 +- .../MockCCLicenseConnectorServiceImpl.java | 4 +- .../MockCCLicenseConnectorServiceImpl.java | 4 +- pom.xml | 4 +- 49 files changed, 253 insertions(+), 254 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c3c531d59b..1ac1c3742e 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -520,7 +520,7 @@ org.jdom - jdom + jdom2 org.apache.pdfbox diff --git a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java index 89d9ffe5a8..7a60313f8e 100644 --- a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java +++ b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java @@ -52,9 +52,9 @@ import org.dspace.content.service.CommunityService; import org.dspace.core.Context; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; -import org.jdom.Element; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; +import org.jdom2.Element; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -307,7 +307,7 @@ public class StructBuilder { } // finally write the string into the output file. - final org.jdom.Document xmlOutput = new org.jdom.Document(root); + final org.jdom2.Document xmlOutput = new org.jdom2.Document(root); try { new XMLOutputter().output(xmlOutput, output); } catch (IOException e) { @@ -411,7 +411,7 @@ public class StructBuilder { } // Now write the structure out. - org.jdom.Document xmlOutput = new org.jdom.Document(rootElement); + org.jdom2.Document xmlOutput = new org.jdom2.Document(rootElement); try { XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); outputter.output(xmlOutput, output); diff --git a/dspace-api/src/main/java/org/dspace/app/launcher/CommandRunner.java b/dspace-api/src/main/java/org/dspace/app/launcher/CommandRunner.java index ce33b6655b..06c2ddb483 100644 --- a/dspace-api/src/main/java/org/dspace/app/launcher/CommandRunner.java +++ b/dspace-api/src/main/java/org/dspace/app/launcher/CommandRunner.java @@ -16,7 +16,7 @@ import java.io.StreamTokenizer; import java.util.ArrayList; import java.util.List; -import org.jdom.Document; +import org.jdom2.Document; /** * @author mwood diff --git a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java index d445f9bbf3..fcb2098bd0 100644 --- a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java +++ b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java @@ -29,9 +29,9 @@ import org.dspace.scripts.service.ScriptService; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; import org.dspace.services.RequestService; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.input.SAXBuilder; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.input.SAXBuilder; /** * A DSpace script launcher. diff --git a/dspace-api/src/main/java/org/dspace/app/util/GoogleMetadata.java b/dspace-api/src/main/java/org/dspace/app/util/GoogleMetadata.java index 0021f26700..72e2af409f 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/GoogleMetadata.java +++ b/dspace-api/src/main/java/org/dspace/app/util/GoogleMetadata.java @@ -42,7 +42,7 @@ import org.dspace.core.Context; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Element; +import org.jdom2.Element; /** * Configuration and mapping for Google Scholar output metadata diff --git a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java index 97f25cb2b2..474ee4c99c 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java @@ -29,11 +29,11 @@ import org.dspace.discovery.IndexableObject; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.Namespace; -import org.jdom.output.DOMOutputter; -import org.jdom.output.XMLOutputter; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.output.DOMOutputter; +import org.jdom2.output.XMLOutputter; import org.springframework.beans.factory.annotation.Autowired; import org.w3c.dom.Document; @@ -192,7 +192,7 @@ public class OpenSearchServiceImpl implements OpenSearchService { * @param scope - null for the entire repository, or a collection/community handle * @return Service Document */ - protected org.jdom.Document getServiceDocument(String scope) { + protected org.jdom2.Document getServiceDocument(String scope) { ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); Namespace ns = Namespace.getNamespace(osNs); @@ -245,7 +245,7 @@ public class OpenSearchServiceImpl implements OpenSearchService { url.setAttribute("template", template.toString()); root.addContent(url); } - return new org.jdom.Document(root); + return new org.jdom2.Document(root); } /** @@ -255,7 +255,7 @@ public class OpenSearchServiceImpl implements OpenSearchService { * @return W3C Document object * @throws IOException if IO error */ - protected Document jDomToW3(org.jdom.Document jdomDoc) throws IOException { + protected Document jDomToW3(org.jdom2.Document jdomDoc) throws IOException { DOMOutputter domOut = new DOMOutputter(); try { return domOut.output(jdomDoc); diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/AIPDIMCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/AIPDIMCrosswalk.java index 2d919baa9d..4b77e4807a 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/AIPDIMCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/AIPDIMCrosswalk.java @@ -14,8 +14,8 @@ import java.util.List; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * Crosswalk descriptive metadata to and from DIM (DSpace Intermediate diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/AIPTechMDCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/AIPTechMDCrosswalk.java index 8ffddf715f..978cabfb4b 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/AIPTechMDCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/AIPTechMDCrosswalk.java @@ -40,8 +40,8 @@ import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * Crosswalk of technical metadata for DSpace AIP. This is diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMDisseminationCrosswalk.java index 3f4d6bd44e..4365d9a485 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMDisseminationCrosswalk.java @@ -23,8 +23,8 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * DIM dissemination crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMIngestionCrosswalk.java index ad922a65f2..4217308e65 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/DIMIngestionCrosswalk.java @@ -19,8 +19,8 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * DIM ingestion crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/DisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/DisseminationCrosswalk.java index 23e1965d7b..3e4fe21f8f 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/DisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/DisseminationCrosswalk.java @@ -14,8 +14,8 @@ import java.util.List; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * Dissemination Crosswalk plugin -- translate DSpace native diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/IngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/IngestionCrosswalk.java index 7edfb6f79f..bb73c83c45 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/IngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/IngestionCrosswalk.java @@ -14,7 +14,7 @@ import java.util.List; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; -import org.jdom.Element; +import org.jdom2.Element; /** * Ingestion Crosswalk plugin -- translate an external metadata format diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java index e44774a672..b8a4a8aef3 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java @@ -24,11 +24,11 @@ import org.dspace.core.Context; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.Namespace; -import org.jdom.input.SAXBuilder; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.input.SAXBuilder; /** * METS dissemination crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java index 559d463be2..7f6622841b 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSRightsCrosswalk.java @@ -35,8 +35,8 @@ 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.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * METSRights Ingestion and Dissemination Crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java index 182fcebe2f..57202f656e 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/MODSDisseminationCrosswalk.java @@ -15,7 +15,6 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; @@ -42,16 +41,18 @@ import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Attribute; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.Namespace; -import org.jdom.Text; -import org.jdom.Verifier; -import org.jdom.input.SAXBuilder; -import org.jdom.output.XMLOutputter; -import org.jdom.xpath.XPath; +import org.jdom2.Attribute; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.Text; +import org.jdom2.Verifier; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.XMLOutputter; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; /** * Configurable MODS Crosswalk @@ -156,7 +157,7 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin static class modsTriple { public String qdc = null; public Element xml = null; - public XPath xpath = null; + public XPathExpression xpath = null; /** * Initialize from text versions of QDC, XML and XPath. @@ -171,9 +172,9 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin final String postlog = ""; try { result.qdc = qdc; - result.xpath = XPath.newInstance(xpath); - result.xpath.addNamespace(MODS_NS.getPrefix(), MODS_NS.getURI()); - result.xpath.addNamespace(XLINK_NS); + result.xpath = + XPathFactory.instance() + .compile(xpath, Filters.fpassthrough(), null, MODS_NS, XLINK_NS); Document d = builder.build(new StringReader(prolog + xml + postlog)); result.xml = (Element) d.getRootElement().getContent(0); } catch (JDOMException | IOException je) { @@ -295,6 +296,7 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin * @throws IOException if IO error * @throws SQLException if database error * @throws AuthorizeException if authorization error + * @return List of Elements */ @Override public List disseminateList(Context context, DSpaceObject dso) @@ -352,37 +354,29 @@ public class MODSDisseminationCrosswalk extends SelfNamedPlugin if (trip == null) { log.warn("WARNING: " + getPluginInstanceName() + ": No MODS mapping for \"" + qdc + "\""); } else { - try { - Element me = (Element) trip.xml.clone(); - if (addSchema) { - me.setAttribute("schemaLocation", schemaLocation, XSI_NS); - } - Iterator ni = trip.xpath.selectNodes(me).iterator(); - if (!ni.hasNext()) { - log.warn("XPath \"" + trip.xpath.getXPath() + - "\" found no elements in \"" + - outputUgly.outputString(me) + - "\", qdc=" + qdc); - } - while (ni.hasNext()) { - Object what = ni.next(); - if (what instanceof Element) { - ((Element) what).setText(checkedString(value)); - } else if (what instanceof Attribute) { - ((Attribute) what).setValue(checkedString(value)); - } else if (what instanceof Text) { - ((Text) what).setText(checkedString(value)); - } else { - log.warn("Got unknown object from XPath, class=" + what.getClass().getName()); - } - } - result.add(me); - } catch (JDOMException je) { - log.error("Error following XPath in modsTriple: context=" + - outputUgly.outputString(trip.xml) + - ", xpath=" + trip.xpath.getXPath() + ", exception=" + - je.toString()); + Element me = (Element) trip.xml.clone(); + if (addSchema) { + me.setAttribute("schemaLocation", schemaLocation, XSI_NS); } + List matches = trip.xpath.evaluate(me); + if (matches.isEmpty()) { + log.warn("XPath \"" + trip.xpath.getExpression() + + "\" found no elements in \"" + + outputUgly.outputString(me) + + "\", qdc=" + qdc); + } + for (Object match: matches) { + if (match instanceof Element) { + ((Element) match).setText(checkedString(value)); + } else if (match instanceof Attribute) { + ((Attribute) match).setValue(checkedString(value)); + } else if (match instanceof Text) { + ((Text) match).setText(checkedString(value)); + } else { + log.warn("Got unknown object from XPath, class=" + match.getClass().getName()); + } + } + result.add(me); } } return result; diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/NullIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/NullIngestionCrosswalk.java index 994e15601d..562dadaca0 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/NullIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/NullIngestionCrosswalk.java @@ -15,9 +15,9 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; -import org.jdom.Element; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; +import org.jdom2.Element; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; /** * "Null" ingestion crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OAIDCIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OAIDCIngestionCrosswalk.java index 10bd5ce6fa..6b0ecae780 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OAIDCIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OAIDCIngestionCrosswalk.java @@ -20,8 +20,8 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * DIM ingestion crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREDisseminationCrosswalk.java index 3dde093784..ac1c434322 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREDisseminationCrosswalk.java @@ -31,8 +31,8 @@ import org.dspace.core.Context; import org.dspace.core.Utils; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * ORE dissemination crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java index 80c424e782..f756aae225 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java @@ -34,12 +34,13 @@ import org.dspace.content.service.BundleService; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.jdom.Attribute; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.Namespace; -import org.jdom.xpath.XPath; +import org.jdom2.Attribute; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.filter.Filters; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; /** * ORE ingestion crosswalk @@ -113,23 +114,21 @@ public class OREIngestionCrosswalk Document doc = new Document(); doc.addContent(root.detach()); - XPath xpathLinks; List aggregatedResources; String entryId; - try { - xpathLinks = XPath.newInstance("/atom:entry/atom:link[@rel=\"" + ORE_NS.getURI() + "aggregates" + "\"]"); - xpathLinks.addNamespace(ATOM_NS); - aggregatedResources = xpathLinks.selectNodes(doc); + XPathExpression xpathLinks = + XPathFactory.instance() + .compile("/atom:entry/atom:link[@rel=\"" + ORE_NS.getURI() + "aggregates" + "\"]", + Filters.element(), null, ATOM_NS); + aggregatedResources = xpathLinks.evaluate(doc); - xpathLinks = XPath.newInstance("/atom:entry/atom:link[@rel='alternate']/@href"); - xpathLinks.addNamespace(ATOM_NS); - entryId = ((Attribute) xpathLinks.selectSingleNode(doc)).getValue(); - } catch (JDOMException e) { - throw new CrosswalkException("JDOM exception occurred while ingesting the ORE", e); - } + XPathExpression xpathAltHref = + XPathFactory.instance() + .compile("/atom:entry/atom:link[@rel='alternate']/@href", + Filters.attribute(), null, ATOM_NS); + entryId = xpathAltHref.evaluateFirst(doc).getValue(); // Next for each resource, create a bitstream - XPath xpathDesc; NumberFormat nf = NumberFormat.getInstance(); nf.setGroupingUsed(false); nf.setMinimumIntegerDigits(4); @@ -140,16 +139,12 @@ public class OREIngestionCrosswalk String bundleName; Element desc = null; - try { - xpathDesc = XPath.newInstance( - "/atom:entry/oreatom:triples/rdf:Description[@rdf:about=\"" + this.encodeForURL(href) + "\"][1]"); - xpathDesc.addNamespace(ATOM_NS); - xpathDesc.addNamespace(ORE_ATOM); - xpathDesc.addNamespace(RDF_NS); - desc = (Element) xpathDesc.selectSingleNode(doc); - } catch (JDOMException e) { - log.warn("Could not find description for {}", href, e); - } + XPathExpression xpathDesc = + XPathFactory.instance() + .compile("/atom:entry/oreatom:triples/rdf:Description[@rdf:about=\"" + + this.encodeForURL(href) + "\"][1]", + Filters.element(), null, ATOM_NS, ORE_ATOM, RDF_NS); + desc = xpathDesc.evaluateFirst(doc); if (desc != null && desc.getChild("type", RDF_NS).getAttributeValue("resource", RDF_NS) .equals(DS_NS.getURI() + "DSpaceBitstream")) { diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java index e4e387a3ec..39b6c8f29c 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java @@ -30,8 +30,8 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * PREMIS Crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/ParameterizedDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/ParameterizedDisseminationCrosswalk.java index 312aed3543..5d9322339d 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/ParameterizedDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/ParameterizedDisseminationCrosswalk.java @@ -14,7 +14,7 @@ import java.util.Map; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; -import org.jdom.Element; +import org.jdom2.Element; /** * Translate DSpace native metadata into an external XML format, with parameters. diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java index f3c51a5d46..2fdbaaad00 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java @@ -36,10 +36,10 @@ import org.dspace.core.Context; import org.dspace.core.SelfNamedPlugin; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.Namespace; -import org.jdom.input.SAXBuilder; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.input.SAXBuilder; /** * Configurable QDC Crosswalk @@ -290,7 +290,7 @@ public class QDCCrosswalk extends SelfNamedPlugin qdc2element.put(qdc, element); element2qdc.put(makeQualifiedTagName(element), qdc); log.debug("Building Maps: qdc=\"" + qdc + "\", element=\"" + element.toString() + "\""); - } catch (org.jdom.JDOMException je) { + } catch (org.jdom2.JDOMException je) { throw new CrosswalkInternalException( "Failed parsing XML fragment in properties file: \"" + prolog + val + postlog + "\": " + je .toString(), je); @@ -326,6 +326,7 @@ public class QDCCrosswalk extends SelfNamedPlugin * @throws IOException if IO error * @throws SQLException if database error * @throws AuthorizeException if authorization error + * @return List of Elements */ @Override public List disseminateList(Context context, DSpaceObject dso) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/RoleCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/RoleCrosswalk.java index d36ff3edf5..2c763036ce 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/RoleCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/RoleCrosswalk.java @@ -26,12 +26,12 @@ import org.dspace.core.factory.CoreServiceFactory; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.WorkflowException; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.Namespace; -import org.jdom.input.SAXBuilder; -import org.jdom.output.XMLOutputter; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.XMLOutputter; /** * Role Crosswalk diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/SimpleDCDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/SimpleDCDisseminationCrosswalk.java index 22ec68070a..2f91c3aa07 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/SimpleDCDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/SimpleDCDisseminationCrosswalk.java @@ -24,8 +24,8 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.SelfNamedPlugin; -import org.jdom.Element; -import org.jdom.Namespace; +import org.jdom2.Element; +import org.jdom2.Namespace; /** * Disseminator for Simple Dublin Core metadata in XML format. @@ -84,6 +84,7 @@ public class SimpleDCDisseminationCrosswalk extends SelfNamedPlugin * @throws IOException if IO error * @throws SQLException if database error * @throws AuthorizeException if authorization error + * @return List of Elements */ @Override public List disseminateList(Context context, DSpaceObject dso) diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XHTMLHeadDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XHTMLHeadDisseminationCrosswalk.java index d03d2dd887..2fbbdd9756 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XHTMLHeadDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XHTMLHeadDisseminationCrosswalk.java @@ -34,9 +34,9 @@ import org.dspace.core.Context; import org.dspace.core.SelfNamedPlugin; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Element; -import org.jdom.Namespace; -import org.jdom.Verifier; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.Verifier; /** * Crosswalk for creating appropriate <meta> elements to appear in the @@ -178,6 +178,7 @@ public class XHTMLHeadDisseminationCrosswalk * @throws IOException if IO error * @throws SQLException if database error * @throws AuthorizeException if authorization error + * @return List of Elements */ @Override public List disseminateList(Context context, DSpaceObject dso) throws CrosswalkException, diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java index 1c85fd82c5..ba3d5717d6 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTCrosswalk.java @@ -21,7 +21,7 @@ import javax.xml.transform.stream.StreamSource; import org.dspace.core.SelfNamedPlugin; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Namespace; +import org.jdom2.Namespace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java index 6c30c1b1a4..26371b46aa 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTDisseminationCrosswalk.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; @@ -41,14 +42,15 @@ import org.dspace.core.factory.CoreServiceFactory; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.Namespace; -import org.jdom.Verifier; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; -import org.jdom.transform.JDOMResult; -import org.jdom.transform.JDOMSource; +import org.jdom2.Content; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.Verifier; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.jdom2.transform.JDOMResult; +import org.jdom2.transform.JDOMSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -244,6 +246,7 @@ public class XSLTDisseminationCrosswalk * @throws SQLException if database error * @throws AuthorizeException if authorization error * @see DisseminationCrosswalk + * @return List of Elements */ @Override public List disseminateList(Context context, DSpaceObject dso) @@ -268,7 +271,12 @@ public class XSLTDisseminationCrosswalk try { JDOMResult result = new JDOMResult(); xform.transform(new JDOMSource(createDIM(dso).getChildren()), result); - return result.getResult(); + List contentList = result.getResult(); + // Transform List into List + List elementList = contentList.stream() + .filter(obj -> obj instanceof Element) + .map(Element.class::cast).collect(Collectors.toList()); + return elementList; } catch (TransformerException e) { LOG.error("Got error: " + e.toString()); throw new CrosswalkInternalException("XSL translation failed: " + e.toString(), e); diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTIngestionCrosswalk.java index 37a822374d..63ef5f7336 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/XSLTIngestionCrosswalk.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; @@ -34,13 +35,14 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.factory.CoreServiceFactory; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.input.SAXBuilder; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; -import org.jdom.transform.JDOMResult; -import org.jdom.transform.JDOMSource; +import org.jdom2.Content; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.jdom2.transform.JDOMResult; +import org.jdom2.transform.JDOMSource; /** * Configurable XSLT-driven ingestion Crosswalk @@ -141,7 +143,12 @@ public class XSLTIngestionCrosswalk try { JDOMResult result = new JDOMResult(); xform.transform(new JDOMSource(metadata), result); - ingestDIM(context, dso, result.getResult(), createMissingMetadataFields); + List contentList = result.getResult(); + // Transform List into List + List elementList = contentList.stream() + .filter(obj -> obj instanceof Element) + .map(Element.class::cast).collect(Collectors.toList()); + ingestDIM(context, dso, elementList, createMissingMetadataFields); } catch (TransformerException e) { log.error("Got error: " + e.toString()); throw new CrosswalkInternalException("XSL Transformation failed: " + e.toString(), e); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java index 471b9ba27c..03afb5e852 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java @@ -83,10 +83,10 @@ import org.dspace.license.factory.LicenseServiceFactory; import org.dspace.license.service.CreativeCommonsService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Element; -import org.jdom.Namespace; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; /** * Base class for disseminator of diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java index 9a7fffdec5..98277c4f9c 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSIngester.java @@ -51,7 +51,7 @@ import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.WorkflowException; import org.dspace.workflow.factory.WorkflowServiceFactory; -import org.jdom.Element; +import org.jdom2.Element; /** * Base class for package ingester of METS (Metadata Encoding and Transmission diff --git a/dspace-api/src/main/java/org/dspace/content/packager/DSpaceAIPIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/DSpaceAIPIngester.java index 954a68bfc1..e7be7ab511 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/DSpaceAIPIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/DSpaceAIPIngester.java @@ -20,7 +20,7 @@ import org.dspace.content.crosswalk.CrosswalkException; import org.dspace.content.crosswalk.MetadataValidationException; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.jdom.Element; +import org.jdom2.Element; /** * Subclass of the METS packager framework to ingest a DSpace diff --git a/dspace-api/src/main/java/org/dspace/content/packager/DSpaceMETSIngester.java b/dspace-api/src/main/java/org/dspace/content/packager/DSpaceMETSIngester.java index da3965534f..380764268c 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/DSpaceMETSIngester.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/DSpaceMETSIngester.java @@ -23,7 +23,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.core.service.PluginService; -import org.jdom.Element; +import org.jdom2.Element; /** * Packager plugin to ingest a diff --git a/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java b/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java index 8fb8172aeb..3399bdf0f0 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/METSManifest.java @@ -35,15 +35,17 @@ import org.dspace.core.Context; import org.dspace.core.factory.CoreServiceFactory; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Content; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.Namespace; -import org.jdom.input.SAXBuilder; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; -import org.jdom.xpath.XPath; +import org.jdom2.Content; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; /** *

    @@ -382,15 +384,12 @@ public class METSManifest { public List getMdFiles() throws MetadataValidationException { if (mdFiles == null) { - try { - // Use a special namespace with known prefix - // so we get the right prefix. - XPath xpath = XPath.newInstance("descendant::mets:mdRef"); - xpath.addNamespace(metsNS); - mdFiles = xpath.selectNodes(mets); - } catch (JDOMException je) { - throw new MetadataValidationException("Failed while searching for mdRef elements in manifest: ", je); - } + // Use a special namespace with known prefix + // so we get the right prefix. + XPathExpression xpath = + XPathFactory.instance() + .compile("descendant::mets:mdRef", Filters.element(), null, metsNS); + mdFiles = xpath.evaluate(mets); } return mdFiles; } @@ -414,25 +413,22 @@ public class METSManifest { return null; } - try { - XPath xpath = XPath.newInstance( - "mets:fileSec/mets:fileGrp[@USE=\"CONTENT\"]/mets:file[@GROUPID=\"" + groupID + "\"]"); - xpath.addNamespace(metsNS); - List oFiles = xpath.selectNodes(mets); - if (oFiles.size() > 0) { - if (log.isDebugEnabled()) { - log.debug("Got ORIGINAL file for derived=" + file.toString()); - } - Element flocat = ((Element) oFiles.get(0)).getChild("FLocat", metsNS); - if (flocat != null) { - return flocat.getAttributeValue("href", xlinkNS); - } + XPathExpression xpath = + XPathFactory.instance() + .compile( + "mets:fileSec/mets:fileGrp[@USE=\"CONTENT\"]/mets:file[@GROUPID=\"" + groupID + "\"]", + Filters.element(), null, metsNS); + List oFiles = xpath.evaluate(mets); + if (oFiles.size() > 0) { + if (log.isDebugEnabled()) { + log.debug("Got ORIGINAL file for derived=" + file.toString()); + } + Element flocat = oFiles.get(0).getChild("FLocat", metsNS); + if (flocat != null) { + return flocat.getAttributeValue("href", xlinkNS); } - return null; - } catch (JDOMException je) { - log.warn("Got exception on XPATH looking for Original file, " + je.toString()); - return null; } + return null; } // translate bundle name from METS to DSpace; METS may be "CONTENT" @@ -888,20 +884,16 @@ public class METSManifest { // use only when path varies each time you call it. protected Element getElementByXPath(String path, boolean nullOk) throws MetadataValidationException { - try { - XPath xpath = XPath.newInstance(path); - xpath.addNamespace(metsNS); - xpath.addNamespace(xlinkNS); - Object result = xpath.selectSingleNode(mets); - if (result == null && nullOk) { - return null; - } else if (result instanceof Element) { - return (Element) result; - } else { - throw new MetadataValidationException("METSManifest: Failed to resolve XPath, path=\"" + path + "\""); - } - } catch (JDOMException je) { - throw new MetadataValidationException("METSManifest: Failed to resolve XPath, path=\"" + path + "\"", je); + XPathExpression xpath = + XPathFactory.instance() + .compile(path, Filters.element(), null, metsNS, xlinkNS); + Element result = xpath.evaluateFirst(mets); + if (result == null && nullOk) { + return null; + } else if (result == null && !nullOk) { + throw new MetadataValidationException("METSManifest: Failed to resolve XPath, path=\"" + path + "\""); + } else { + return result; } } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java index 8643f60f6c..f627779af8 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/RoleDisseminator.java @@ -35,7 +35,7 @@ import org.dspace.eperson.PasswordHash; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; -import org.jdom.Namespace; +import org.jdom2.Namespace; /** * Plugin to export all Group and EPerson objects in XML, perhaps for reloading. diff --git a/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollectionServiceImpl.java index 88cec74a58..d4bb37cf94 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollectionServiceImpl.java @@ -29,10 +29,10 @@ import org.dspace.harvest.dao.HarvestedCollectionDAO; import org.dspace.harvest.service.HarvestedCollectionService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.Namespace; -import org.jdom.input.DOMBuilder; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.input.DOMBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.w3c.dom.DOMException; import org.xml.sax.SAXException; 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 71e00d73d7..a15ed53a93 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java +++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java @@ -70,11 +70,11 @@ import org.dspace.harvest.service.HarvestedCollectionService; import org.dspace.harvest.service.HarvestedItemService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.Namespace; -import org.jdom.input.DOMBuilder; -import org.jdom.output.XMLOutputter; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.input.DOMBuilder; +import org.jdom2.output.XMLOutputter; import org.xml.sax.SAXException; diff --git a/dspace-api/src/main/java/org/dspace/identifier/DataCiteXMLCreator.java b/dspace-api/src/main/java/org/dspace/identifier/DataCiteXMLCreator.java index 0ea25ff3a4..ae2cd248d4 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DataCiteXMLCreator.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DataCiteXMLCreator.java @@ -23,8 +23,8 @@ import org.dspace.core.factory.CoreServiceFactory; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; -import org.jdom.Element; -import org.jdom.output.XMLOutputter; +import org.jdom2.Element; +import org.jdom2.output.XMLOutputter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 bc8ea90957..57136d6143 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 @@ -45,13 +45,13 @@ import org.dspace.core.factory.CoreServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.identifier.DOI; import org.dspace.services.ConfigurationService; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.filter.ElementFilter; -import org.jdom.input.SAXBuilder; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.filter.ElementFilter; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorService.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorService.java index 0c061d2d64..64450b796c 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorService.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorService.java @@ -10,7 +10,7 @@ package org.dspace.license; import java.io.IOException; import java.util.Map; -import org.jdom.Document; +import org.jdom2.Document; /** * Service interface class for the Creative commons license connector service. 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 792c25d629..e0039169ba 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -34,11 +34,11 @@ import org.apache.logging.log4j.Logger; import org.dspace.services.ConfigurationService; import org.jaxen.JaxenException; import org.jaxen.jdom.JDOMXPath; -import org.jdom.Attribute; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.input.SAXBuilder; +import org.jdom2.Attribute; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.input.SAXBuilder; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.xml.sax.InputSource; @@ -141,7 +141,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, try (StringReader stringReader = new StringReader(responseString)) { InputSource is = new InputSource(stringReader); - org.jdom.Document classDoc = this.parser.build(is); + org.jdom2.Document classDoc = this.parser.build(is); List elements = licenseClassXpath.selectNodes(classDoc); for (Element element : elements) { @@ -179,7 +179,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, try (StringReader stringReader = new StringReader(responseString)) { InputSource is = new InputSource(stringReader); - org.jdom.Document classDoc = this.parser.build(is); + org.jdom2.Document classDoc = this.parser.build(is); Object element = licenseClassXpath.selectSingleNode(classDoc); String licenseLabel = getSingleNodeValue(element, "label"); @@ -298,7 +298,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, try (StringReader stringReader = new StringReader(responseString)) { InputSource is = new InputSource(stringReader); - org.jdom.Document classDoc = this.parser.build(is); + org.jdom2.Document classDoc = this.parser.build(is); Object node = licenseClassXpath.selectSingleNode(classDoc); String nodeValue = getNodeValue(node); 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 ccc660b63b..96f110c101 100644 --- a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java @@ -40,8 +40,8 @@ import org.dspace.core.Utils; import org.dspace.license.service.CreativeCommonsService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Document; -import org.jdom.transform.JDOMSource; +import org.jdom2.Document; +import org.jdom2.transform.JDOMSource; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; diff --git a/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java b/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java index 1f5f1ddd02..0f4911aa3e 100644 --- a/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java +++ b/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java @@ -18,7 +18,7 @@ import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.license.CCLicense; -import org.jdom.Document; +import org.jdom2.Document; /** * Service interface class for the Creative commons licensing. diff --git a/dspace-api/src/main/java/org/dspace/testing/PubMedToImport.java b/dspace-api/src/main/java/org/dspace/testing/PubMedToImport.java index b7ded5ecbf..ec51528429 100644 --- a/dspace-api/src/main/java/org/dspace/testing/PubMedToImport.java +++ b/dspace-api/src/main/java/org/dspace/testing/PubMedToImport.java @@ -24,10 +24,10 @@ 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.jdom.Document; -import org.jdom.Element; -import org.jdom.output.Format; -import org.jdom.output.XMLOutputter; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; diff --git a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java index 402947b966..cada4944e3 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java @@ -32,7 +32,7 @@ import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.MockSolrLoggerServiceImpl; import org.dspace.statistics.MockSolrStatisticsCore; import org.dspace.storage.rdbms.DatabaseUtils; -import org.jdom.Document; +import org.jdom2.Document; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java index c814d2d9f6..7d808ab871 100644 --- a/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java +++ b/dspace-api/src/test/java/org/dspace/app/packager/PackagerIT.java @@ -38,7 +38,7 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Element; +import org.jdom2.Element; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/dspace-api/src/test/java/org/dspace/content/crosswalk/QDCCrosswalkTest.java b/dspace-api/src/test/java/org/dspace/content/crosswalk/QDCCrosswalkTest.java index efed8ad8dc..2eafc03986 100644 --- a/dspace-api/src/test/java/org/dspace/content/crosswalk/QDCCrosswalkTest.java +++ b/dspace-api/src/test/java/org/dspace/content/crosswalk/QDCCrosswalkTest.java @@ -14,7 +14,7 @@ import org.dspace.AbstractDSpaceTest; import org.dspace.core.service.PluginService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; -import org.jdom.Namespace; +import org.jdom2.Namespace; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; diff --git a/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java b/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java index 8545c4187d..30a5a3a9b5 100644 --- a/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java @@ -15,8 +15,8 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.jdom.Document; -import org.jdom.JDOMException; +import org.jdom2.Document; +import org.jdom2.JDOMException; /** * Mock implementation for the Creative commons license connector service. diff --git a/dspace-server-webapp/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java b/dspace-server-webapp/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java index bb443ab4a4..d70499d096 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java +++ b/dspace-server-webapp/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java @@ -15,8 +15,8 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.jdom.Document; -import org.jdom.JDOMException; +import org.jdom2.Document; +import org.jdom2.JDOMException; /** * Mock implementation for the Creative commons license connector service. diff --git a/pom.xml b/pom.xml index 27f6597caa..4d140a33dd 100644 --- a/pom.xml +++ b/pom.xml @@ -1481,8 +1481,8 @@ org.jdom - jdom - 1.1.3 + jdom2 + 2.0.6.1 From d6df493080fbc33d2061468a3c2b7944d59ab5e7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 16 Mar 2022 17:19:08 -0500 Subject: [PATCH 0766/1254] Fix broken tests by replacing JDOMXPath with XPathFactory --- .../CCLicenseConnectorServiceImpl.java | 68 +++++++++---------- pom.xml | 1 + 2 files changed, 32 insertions(+), 37 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 e0039169ba..619227432d 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -32,13 +32,14 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.Logger; import org.dspace.services.ConfigurationService; -import org.jaxen.JaxenException; -import org.jaxen.jdom.JDOMXPath; import org.jdom2.Attribute; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; +import org.jdom2.filter.Filters; import org.jdom2.input.SAXBuilder; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.xml.sax.InputSource; @@ -96,7 +97,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, List licenses; try (CloseableHttpResponse response = client.execute(httpGet)) { licenses = retrieveLicenses(response); - } catch (JDOMException | JaxenException | IOException e) { + } catch (JDOMException | IOException e) { log.error("Error while retrieving the license details using url: " + uri, e); licenses = Collections.emptyList(); } @@ -110,7 +111,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, try (CloseableHttpResponse response = client.execute(licenseHttpGet)) { CCLicense ccLicense = retrieveLicenseObject(license, response); ccLicenses.put(ccLicense.getLicenseId(), ccLicense); - } catch (JaxenException | JDOMException | IOException e) { + } catch (JDOMException | IOException e) { log.error("Error while retrieving the license details using url: " + licenseUri, e); } } @@ -125,25 +126,23 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, * @param response The response from the API * @return a list of license identifiers for which details need to be retrieved * @throws IOException - * @throws JaxenException * @throws JDOMException */ private List retrieveLicenses(CloseableHttpResponse response) - throws IOException, JaxenException, JDOMException { + throws IOException, JDOMException { List domains = new LinkedList<>(); String[] excludedLicenses = configurationService.getArrayProperty("cc.license.classfilter"); - String responseString = EntityUtils.toString(response.getEntity()); - JDOMXPath licenseClassXpath = new JDOMXPath("//licenses/license"); - + XPathExpression licenseClassXpath = + XPathFactory.instance().compile("//licenses/license", Filters.element()); try (StringReader stringReader = new StringReader(responseString)) { InputSource is = new InputSource(stringReader); org.jdom2.Document classDoc = this.parser.build(is); - List elements = licenseClassXpath.selectNodes(classDoc); + List elements = licenseClassXpath.evaluate(classDoc); for (Element element : elements) { String licenseId = getSingleNodeValue(element, "@id"); if (StringUtils.isNotBlank(licenseId) && !ArrayUtils.contains(excludedLicenses, licenseId)) { @@ -163,30 +162,29 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, * @param response for a specific CC License response * @return the corresponding CC License Object * @throws IOException - * @throws JaxenException * @throws JDOMException */ private CCLicense retrieveLicenseObject(final String licenseId, CloseableHttpResponse response) - throws IOException, JaxenException, JDOMException { + throws IOException, JDOMException { String responseString = EntityUtils.toString(response.getEntity()); - - JDOMXPath licenseClassXpath = new JDOMXPath("//licenseclass"); - JDOMXPath licenseFieldXpath = new JDOMXPath("field"); - + XPathExpression licenseClassXpath = + XPathFactory.instance().compile("//licenseclass", Filters.fpassthrough()); + XPathExpression licenseFieldXpath = + XPathFactory.instance().compile("field", Filters.element()); try (StringReader stringReader = new StringReader(responseString)) { InputSource is = new InputSource(stringReader); org.jdom2.Document classDoc = this.parser.build(is); - Object element = licenseClassXpath.selectSingleNode(classDoc); + Object element = licenseClassXpath.evaluateFirst(classDoc); String licenseLabel = getSingleNodeValue(element, "label"); List ccLicenseFields = new LinkedList<>(); - List licenseFields = licenseFieldXpath.selectNodes(element); + List licenseFields = licenseFieldXpath.evaluate(element); for (Element licenseField : licenseFields) { CCLicenseField ccLicenseField = parseLicenseField(licenseField); ccLicenseFields.add(ccLicenseField); @@ -196,13 +194,14 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, } } - private CCLicenseField parseLicenseField(final Element licenseField) throws JaxenException { + private CCLicenseField parseLicenseField(final Element licenseField) { String id = getSingleNodeValue(licenseField, "@id"); String label = getSingleNodeValue(licenseField, "label"); String description = getSingleNodeValue(licenseField, "description"); - JDOMXPath enumXpath = new JDOMXPath("enum"); - List enums = enumXpath.selectNodes(licenseField); + XPathExpression enumXpath = + XPathFactory.instance().compile("enum", Filters.element()); + List enums = enumXpath.evaluate(licenseField); List ccLicenseFieldEnumList = new LinkedList<>(); @@ -215,7 +214,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, } - private CCLicenseFieldEnum parseEnum(final Element enumElement) throws JaxenException { + private CCLicenseFieldEnum parseEnum(final Element enumElement) { String id = getSingleNodeValue(enumElement, "@id"); String label = getSingleNodeValue(enumElement, "label"); String description = getSingleNodeValue(enumElement, "description"); @@ -236,9 +235,10 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, } } - private String getSingleNodeValue(final Object t, String query) throws JaxenException { - JDOMXPath xpath = new JDOMXPath(query); - Object singleNode = xpath.selectSingleNode(t); + private String getSingleNodeValue(final Object t, String query) { + XPathExpression xpath = + XPathFactory.instance().compile(query, Filters.fpassthrough()); + Object singleNode = xpath.evaluateFirst(t); return getNodeValue(singleNode); } @@ -273,7 +273,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, try (CloseableHttpResponse response = client.execute(httpPost)) { return retrieveLicenseUri(response); - } catch (JDOMException | JaxenException | IOException e) { + } catch (JDOMException | IOException e) { log.error("Error while retrieving the license uri for license : " + licenseId + " with answers " + answerMap.toString(), e); } @@ -286,21 +286,20 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, * @param response for a specific CC License URI response * @return the corresponding CC License URI as a string * @throws IOException - * @throws JaxenException * @throws JDOMException */ private String retrieveLicenseUri(final CloseableHttpResponse response) - throws IOException, JaxenException, JDOMException { + throws IOException, JDOMException { String responseString = EntityUtils.toString(response.getEntity()); - JDOMXPath licenseClassXpath = new JDOMXPath("//result/license-uri"); - + XPathExpression licenseClassXpath = + XPathFactory.instance().compile("//result/license-uri", Filters.fpassthrough()); try (StringReader stringReader = new StringReader(responseString)) { InputSource is = new InputSource(stringReader); org.jdom2.Document classDoc = this.parser.build(is); - Object node = licenseClassXpath.selectSingleNode(classDoc); + Object node = licenseClassXpath.evaluateFirst(classDoc); String nodeValue = getNodeValue(node); if (StringUtils.isNotBlank(nodeValue)) { @@ -364,12 +363,7 @@ public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, * @return the license name */ public String retrieveLicenseName(final Document doc) { - try { - return getSingleNodeValue(doc, "//result/license-name"); - } catch (JaxenException e) { - log.error("Error while retrieving the license name from the license document", e); - } - return null; + return getSingleNodeValue(doc, "//result/license-name"); } } diff --git a/pom.xml b/pom.xml index 4d140a33dd..55cffa4083 100644 --- a/pom.xml +++ b/pom.xml @@ -1468,6 +1468,7 @@ 3.1.0 + jaxen jaxen From 27fdab0dfb8543a5c533006efa2276915cde16d8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 17 Mar 2022 15:53:30 -0500 Subject: [PATCH 0767/1254] Upgrade Rome to avoid pulling in old version of JDOM --- dspace-api/pom.xml | 7 +++- .../app/util/OpenSearchServiceImpl.java | 8 ++-- .../org/dspace/app/util/SyndicationFeed.java | 40 +++++++++---------- pom.xml | 18 +++++++++ 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 1ac1c3742e..d5759d415e 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -581,9 +581,12 @@ - org.rometools + com.rometools + rome + + + com.rometools rome-modules - 1.0 org.jbibtex diff --git a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java index 474ee4c99c..89ca477442 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/util/OpenSearchServiceImpl.java @@ -16,10 +16,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import com.sun.syndication.feed.module.opensearch.OpenSearchModule; -import com.sun.syndication.feed.module.opensearch.entity.OSQuery; -import com.sun.syndication.feed.module.opensearch.impl.OpenSearchModuleImpl; -import com.sun.syndication.io.FeedException; +import com.rometools.modules.opensearch.OpenSearchModule; +import com.rometools.modules.opensearch.entity.OSQuery; +import com.rometools.modules.opensearch.impl.OpenSearchModuleImpl; +import com.rometools.rome.io.FeedException; import org.apache.logging.log4j.Logger; import org.dspace.app.util.service.OpenSearchService; import org.dspace.content.DSpaceObject; diff --git a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java index 2576df0193..8bbec234c9 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java @@ -15,26 +15,26 @@ import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; -import com.sun.syndication.feed.module.DCModule; -import com.sun.syndication.feed.module.DCModuleImpl; -import com.sun.syndication.feed.module.Module; -import com.sun.syndication.feed.module.itunes.EntryInformation; -import com.sun.syndication.feed.module.itunes.EntryInformationImpl; -import com.sun.syndication.feed.module.itunes.types.Duration; -import com.sun.syndication.feed.synd.SyndContent; -import com.sun.syndication.feed.synd.SyndContentImpl; -import com.sun.syndication.feed.synd.SyndEnclosure; -import com.sun.syndication.feed.synd.SyndEnclosureImpl; -import com.sun.syndication.feed.synd.SyndEntry; -import com.sun.syndication.feed.synd.SyndEntryImpl; -import com.sun.syndication.feed.synd.SyndFeed; -import com.sun.syndication.feed.synd.SyndFeedImpl; -import com.sun.syndication.feed.synd.SyndImage; -import com.sun.syndication.feed.synd.SyndImageImpl; -import com.sun.syndication.feed.synd.SyndPerson; -import com.sun.syndication.feed.synd.SyndPersonImpl; -import com.sun.syndication.io.FeedException; -import com.sun.syndication.io.SyndFeedOutput; +import com.rometools.modules.itunes.EntryInformation; +import com.rometools.modules.itunes.EntryInformationImpl; +import com.rometools.modules.itunes.types.Duration; +import com.rometools.rome.feed.module.DCModule; +import com.rometools.rome.feed.module.DCModuleImpl; +import com.rometools.rome.feed.module.Module; +import com.rometools.rome.feed.synd.SyndContent; +import com.rometools.rome.feed.synd.SyndContentImpl; +import com.rometools.rome.feed.synd.SyndEnclosure; +import com.rometools.rome.feed.synd.SyndEnclosureImpl; +import com.rometools.rome.feed.synd.SyndEntry; +import com.rometools.rome.feed.synd.SyndEntryImpl; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndFeedImpl; +import com.rometools.rome.feed.synd.SyndImage; +import com.rometools.rome.feed.synd.SyndImageImpl; +import com.rometools.rome.feed.synd.SyndPerson; +import com.rometools.rome.feed.synd.SyndPersonImpl; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.SyndFeedOutput; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; diff --git a/pom.xml b/pom.xml index 55cffa4083..4dfe21d9a6 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ 2.17.1 2.0.24 3.17 + 1.18.0 1.7.25 @@ -1161,6 +1162,23 @@ ${hibernate-validator.version} + + + com.rometools + rome + ${rome.version} + + + com.rometools + rome-modules + ${rome.version} + + + com.rometools + rome-utils + ${rome.version} + + org.springframework spring-orm From 8ed314f60870ab5f32c40f23c44f4f2b0aa0e2fb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Mar 2022 17:24:59 -0500 Subject: [PATCH 0768/1254] Remove Axiom from dspace-api, replacing it with JDOM2 --- dspace-api/pom.xml | 38 --------- .../ArXivIdMetadataContributor.java | 4 +- .../ArXivImportMetadataSourceServiceImpl.java | 69 +++++++++------- .../SimpleXpathMetadatumContributor.java | 78 ++++++++++--------- ...PubmedImportMetadataSourceServiceImpl.java | 67 +++++++++------- dspace-swordv2/pom.xml | 3 +- pom.xml | 3 +- 7 files changed, 125 insertions(+), 137 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d5759d415e..66ff4e69bc 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -818,44 +818,6 @@ jaxb-runtime - - - org.apache.ws.commons.axiom - axiom-impl - ${axiom.version} - - - - org.apache.geronimo.specs - * - - - - org.codehaus.woodstox - woodstox-core-asl - - - - - org.apache.ws.commons.axiom - axiom-api - ${axiom.version} - - - - org.apache.geronimo.specs - * - - - - org.codehaus.woodstox - woodstox-core-asl - - - - org.glassfish.jersey.core diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java index ed5ac5960b..7bd42cf07a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/metadatamapping/contributor/ArXivIdMetadataContributor.java @@ -9,10 +9,10 @@ package org.dspace.importer.external.arxiv.metadatamapping.contributor; import java.util.Collection; -import org.apache.axiom.om.OMElement; import org.dspace.importer.external.metadatamapping.MetadatumDTO; import org.dspace.importer.external.metadatamapping.contributor.MetadataContributor; import org.dspace.importer.external.metadatamapping.contributor.SimpleXpathMetadatumContributor; +import org.jdom2.Element; /** * Arxiv specific implementation of {@link MetadataContributor} @@ -32,7 +32,7 @@ public class ArXivIdMetadataContributor extends SimpleXpathMetadatumContributor * @return a collection of import records. Only the identifier of the found records may be put in the record. */ @Override - public Collection contributeMetadata(OMElement t) { + public Collection contributeMetadata(Element t) { Collection values = super.contributeMetadata(t); parseValue(values); return values; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java index 6b418423fa..96689e62ba 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/arxiv/service/ArXivImportMetadataSourceServiceImpl.java @@ -7,8 +7,10 @@ */ package org.dspace.importer.external.arxiv.service; +import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; @@ -20,10 +22,6 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.axiom.om.OMElement; -import org.apache.axiom.om.OMXMLBuilderFactory; -import org.apache.axiom.om.OMXMLParserWrapper; -import org.apache.axiom.om.xpath.AXIOMXPath; import org.apache.commons.lang3.StringUtils; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; @@ -31,7 +29,14 @@ import org.dspace.importer.external.datamodel.Query; import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.service.AbstractImportMetadataSourceService; import org.dspace.importer.external.service.components.QuerySource; -import org.jaxen.JaxenException; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; /** * Implements a data source for querying ArXiv @@ -39,7 +44,7 @@ import org.jaxen.JaxenException; * @author Pasquale Cavallo (pasquale.cavallo at 4Science dot it) * */ -public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService +public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService implements QuerySource { private WebTarget webTarget; @@ -213,15 +218,20 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata Response response = invocationBuilder.get(); if (response.getStatus() == 200) { String responseString = response.readEntity(String.class); - OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(responseString)); - OMElement element = records.getDocumentElement(); - AXIOMXPath xpath = null; + + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(responseString)); + Element root = document.getRootElement(); + + List namespaces = Arrays.asList(Namespace.getNamespace("opensearch", + "http://a9.com/-/spec/opensearch/1.1/")); + XPathExpression xpath = + XPathFactory.instance().compile("opensearch:totalResults", Filters.element(), null, namespaces); + + Element count = xpath.evaluateFirst(root); try { - xpath = new AXIOMXPath("opensearch:totalResults"); - xpath.addNamespace("opensearch", "http://a9.com/-/spec/opensearch/1.1/"); - OMElement count = (OMElement) xpath.selectSingleNode(element); return Integer.parseInt(count.getText()); - } catch (JaxenException e) { + } catch (NumberFormatException e) { return null; } } else { @@ -274,8 +284,8 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata Response response = invocationBuilder.get(); if (response.getStatus() == 200) { String responseString = response.readEntity(String.class); - List omElements = splitToRecords(responseString); - for (OMElement record : omElements) { + List elements = splitToRecords(responseString); + for (Element record : elements) { results.add(transformSourceRecords(record)); } return results; @@ -321,8 +331,8 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata Response response = invocationBuilder.get(); if (response.getStatus() == 200) { String responseString = response.readEntity(String.class); - List omElements = splitToRecords(responseString); - for (OMElement record : omElements) { + List elements = splitToRecords(responseString); + for (Element record : elements) { results.add(transformSourceRecords(record)); } return results; @@ -359,8 +369,8 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata Response response = invocationBuilder.get(); if (response.getStatus() == 200) { String responseString = response.readEntity(String.class); - List omElements = splitToRecords(responseString); - for (OMElement record : omElements) { + List elements = splitToRecords(responseString); + for (Element record : elements) { results.add(transformSourceRecords(record)); } return results; @@ -387,16 +397,21 @@ public class ArXivImportMetadataSourceServiceImpl extends AbstractImportMetadata } } - private List splitToRecords(String recordsSrc) { - OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(recordsSrc)); - OMElement element = records.getDocumentElement(); - AXIOMXPath xpath = null; + private List splitToRecords(String recordsSrc) { + try { - xpath = new AXIOMXPath("ns:entry"); - xpath.addNamespace("ns", "http://www.w3.org/2005/Atom"); - List recordsList = xpath.selectNodes(element); + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(recordsSrc)); + Element root = document.getRootElement(); + + List namespaces = Arrays.asList(Namespace.getNamespace("ns", + "http://www.w3.org/2005/Atom")); + XPathExpression xpath = + XPathFactory.instance().compile("ns:entry", Filters.element(), null, namespaces); + + List recordsList = xpath.evaluate(root); return recordsList; - } catch (JaxenException e) { + } catch (JDOMException | IOException e) { return null; } } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java index 87cdbfa6ed..65d6d66947 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java @@ -7,33 +7,36 @@ */ package org.dspace.importer.external.metadatamapping.contributor; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.annotation.Resource; -import org.apache.axiom.om.OMAttribute; -import org.apache.axiom.om.OMElement; -import org.apache.axiom.om.OMText; -import org.apache.axiom.om.xpath.AXIOMXPath; +import org.apache.logging.log4j.Logger; import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; import org.dspace.importer.external.metadatamapping.MetadatumDTO; -import org.jaxen.JaxenException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.jdom2.Attribute; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.Text; +import org.jdom2.filter.Filters; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; import org.springframework.beans.factory.annotation.Autowired; /** - * Metadata contributor that takes an axiom OMElement and turns it into a metadatum + * Metadata contributor that takes a JDOM Element and turns it into a metadatum * * @author Roeland Dillen (roeland at atmire dot com) */ -public class SimpleXpathMetadatumContributor implements MetadataContributor { +public class SimpleXpathMetadatumContributor implements MetadataContributor { private MetadataFieldConfig field; - private static final Logger log = LoggerFactory.getLogger(SimpleXpathMetadatumContributor.class); + private static final Logger log + = org.apache.logging.log4j.LogManager.getLogger(); /** * Return prefixToNamespaceMapping @@ -44,14 +47,14 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor> metadataFieldMapping; + private MetadataFieldMapping> metadataFieldMapping; /** * Return metadataFieldMapping * * @return MetadataFieldMapping */ - public MetadataFieldMapping> getMetadataFieldMapping() { + public MetadataFieldMapping> getMetadataFieldMapping() { return metadataFieldMapping; } @@ -62,7 +65,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor> metadataFieldMapping) { + MetadataFieldMapping> metadataFieldMapping) { this.metadataFieldMapping = metadataFieldMapping; } @@ -140,36 +143,35 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor contributeMetadata(OMElement t) { + public Collection contributeMetadata(Element t) { List values = new LinkedList<>(); - try { - AXIOMXPath xpath = new AXIOMXPath(query); - for (String ns : prefixToNamespaceMapping.keySet()) { - xpath.addNamespace(prefixToNamespaceMapping.get(ns), ns); - } - List nodes = xpath.selectNodes(t); - for (Object el : nodes) { - if (el instanceof OMElement) { - values.add(metadataFieldMapping.toDCValue(field, ((OMElement) el).getText())); - } else if (el instanceof OMAttribute) { - values.add(metadataFieldMapping.toDCValue(field, ((OMAttribute) el).getAttributeValue())); - } else if (el instanceof String) { - values.add(metadataFieldMapping.toDCValue(field, (String) el)); - } else if (el instanceof OMText) { - values.add(metadataFieldMapping.toDCValue(field, ((OMText) el).getText())); - } else { - log.error("node of type: " + el.getClass()); - } - } - return values; - } catch (JaxenException e) { - log.error(query, e); - throw new RuntimeException(e); + + List namespaces = new ArrayList<>(); + for (String ns : prefixToNamespaceMapping.keySet()) { + namespaces.add(Namespace.getNamespace(prefixToNamespaceMapping.get(ns), ns)); } + XPathExpression xpath = + XPathFactory.instance().compile(query, Filters.fpassthrough(), null, namespaces); + + List nodes = xpath.evaluate(t); + for (Object el : nodes) { + if (el instanceof Element) { + values.add(metadataFieldMapping.toDCValue(field, ((Element) el).getText())); + } else if (el instanceof Attribute) { + values.add(metadataFieldMapping.toDCValue(field, ((Attribute) el).getValue())); + } else if (el instanceof String) { + values.add(metadataFieldMapping.toDCValue(field, (String) el)); + } else if (el instanceof Text) { + values.add(metadataFieldMapping.toDCValue(field, ((Text) el).getText())); + } else { + log.error("Encountered unsupported XML node of type: {}. Skipped that node.", el.getClass()); + } + } + return values; } } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java index 4802dcfa17..73768330d8 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmed/service/PubmedImportMetadataSourceServiceImpl.java @@ -25,10 +25,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import com.google.common.io.CharStreams; -import org.apache.axiom.om.OMElement; -import org.apache.axiom.om.OMXMLBuilderFactory; -import org.apache.axiom.om.OMXMLParserWrapper; -import org.apache.axiom.om.xpath.AXIOMXPath; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; @@ -38,7 +34,13 @@ import org.dspace.importer.external.exception.MetadataSourceException; import org.dspace.importer.external.service.AbstractImportMetadataSourceService; import org.dspace.importer.external.service.components.FileSource; import org.dspace.importer.external.service.components.QuerySource; -import org.jaxen.JaxenException; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; /** * Implements a data source for querying PubMed Central @@ -46,7 +48,7 @@ import org.jaxen.JaxenException; * @author Roeland Dillen (roeland at atmire dot com) * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) */ -public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService +public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService implements QuerySource, FileSource { private String baseAddress; @@ -59,7 +61,7 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat /** * 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; @@ -243,17 +245,21 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat private String getSingleElementValue(String src, String elementName) { - OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(src)); - OMElement element = records.getDocumentElement(); - AXIOMXPath xpath = null; String value = null; + try { - xpath = new AXIOMXPath("//" + elementName); - List recordsList = xpath.selectNodes(element); - if (!recordsList.isEmpty()) { - value = recordsList.get(0).getText(); + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(src)); + Element root = document.getRootElement(); + + XPathExpression xpath = + XPathFactory.instance().compile("//" + elementName, Filters.element()); + + Element record = xpath.evaluateFirst(root); + if (record != null) { + value = record.getText(); } - } catch (JaxenException e) { + } catch (JDOMException | IOException e) { value = null; } return value; @@ -314,9 +320,9 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat invocationBuilder = getRecordsTarget.request(MediaType.TEXT_PLAIN_TYPE); response = invocationBuilder.get(); - List omElements = splitToRecords(response.readEntity(String.class)); + List elements = splitToRecords(response.readEntity(String.class)); - for (OMElement record : omElements) { + for (Element record : elements) { records.add(transformSourceRecords(record)); } @@ -324,15 +330,18 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat } } - private List splitToRecords(String recordsSrc) { - OMXMLParserWrapper records = OMXMLBuilderFactory.createOMBuilder(new StringReader(recordsSrc)); - OMElement element = records.getDocumentElement(); - AXIOMXPath xpath = null; + private List splitToRecords(String recordsSrc) { try { - xpath = new AXIOMXPath("//PubmedArticle"); - List recordsList = xpath.selectNodes(element); + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(recordsSrc)); + Element root = document.getRootElement(); + + XPathExpression xpath = + XPathFactory.instance().compile("//PubmedArticle", Filters.element()); + + List recordsList = xpath.evaluate(root); return recordsList; - } catch (JaxenException e) { + } catch (JDOMException | IOException e) { return null; } } @@ -362,13 +371,13 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat Response response = invocationBuilder.get(); - List omElements = splitToRecords(response.readEntity(String.class)); + List elements = splitToRecords(response.readEntity(String.class)); - if (omElements.size() == 0) { + if (elements.isEmpty()) { return null; } - return transformSourceRecords(omElements.get(0)); + return transformSourceRecords(elements.get(0)); } } @@ -441,8 +450,8 @@ public class PubmedImportMetadataSourceServiceImpl extends AbstractImportMetadat private List parseXMLString(String xml) { List records = new LinkedList(); - List omElements = splitToRecords(xml); - for (OMElement record : omElements) { + List elements = splitToRecords(xml); + for (Element record : elements) { records.add(transformSourceRecords(record)); } return records; diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 3fbc6cf469..6bf09475ef 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -135,7 +135,8 @@ org.apache.ws.commons.axiom fom-impl - ${axiom.version} + + 1.2.22 diff --git a/pom.xml b/pom.xml index 4dfe21d9a6..e03dcb3b07 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,6 @@ 42.3.3 8.8.1 - 1.2.22 3.4.0 2.10.0 @@ -1117,7 +1116,7 @@ 3.4.14 - + org.apache.james apache-mime4j-core From aeac1ee1ea09eb180135ad2515e1b39370422516 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 23 Mar 2022 22:37:00 +0100 Subject: [PATCH 0769/1254] 88675: Fix place of opposite item --- .../DefaultItemVersionProvider.java | 12 +- .../VersioningWithRelationshipsTest.java | 300 +++++++++--------- 2 files changed, 161 insertions(+), 151 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java index d4590ae24e..e12acfca33 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java @@ -148,8 +148,12 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen newItem, // new item oldRelationship.getRightItem(), oldRelationship.getRelationshipType(), + // NOTE: on the side of the new version, we start with an empty list of relationships + // => insert at the same position as the ancestral relationship oldRelationship.getLeftPlace(), - oldRelationship.getRightPlace(), + // NOTE: on the opposite side of the new version, the ancestral relationship already takes our + // desired place => insert AFTER the ancestral relationship + oldRelationship.getRightPlace() + 1, oldRelationship.getLeftwardValue(), oldRelationship.getRightwardValue(), Relationship.LatestVersionStatus.RIGHT_ONLY // only mark the opposite side as "latest" for now @@ -161,7 +165,11 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen oldRelationship.getLeftItem(), newItem, // new item oldRelationship.getRelationshipType(), - oldRelationship.getLeftPlace(), + // NOTE: on the opposite side of the new version, the ancestral relationship already takes our + // desired place => insert AFTER the ancestral relationship + oldRelationship.getLeftPlace() + 1, + // NOTE: on the side of the new version, we start with an empty list of relationships + // => insert at the same position as the ancestral relationship oldRelationship.getRightPlace(), oldRelationship.getLeftwardValue(), oldRelationship.getRightwardValue(), diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index 272d2ccb2b..a1aee64f38 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -139,14 +139,16 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith .build(); } - protected Matcher isRelationship( - Item leftItem, RelationshipType relationshipType, Item rightItem, LatestVersionStatus latestVersionStatus + protected Matcher isRel( + Item leftItem, RelationshipType relationshipType, Item rightItem, LatestVersionStatus latestVersionStatus, + int leftPlace, int rightPlace ) { return allOf( hasProperty("leftItem", is(leftItem)), hasProperty("relationshipType", is(relationshipType)), hasProperty("rightItem", is(rightItem)), - // NOTE: place is not checked + hasProperty("leftPlace", is(leftPlace)), + hasProperty("rightPlace", is(rightPlace)), hasProperty("leftwardValue", nullValue()), hasProperty("rightwardValue", nullValue()), hasProperty("latestVersionStatus", is(latestVersionStatus)) @@ -195,30 +197,30 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -237,39 +239,39 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 1), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 1) )) ); @@ -280,42 +282,42 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 1) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 1) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 1), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 1) )) ); @@ -334,39 +336,39 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 1) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 1) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 1), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 1) )) ); @@ -377,42 +379,42 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 1) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 1) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 1), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 1) )) ); @@ -471,30 +473,30 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -545,16 +547,16 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -562,21 +564,21 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, person2, -1, -1, false, true), containsInAnyOrder(List.of( // NOTE: BOTH because new relationship - isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -584,17 +586,17 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, orgUnit2, -1, -1, false, true), containsInAnyOrder(List.of( // NOTE: BOTH because new relationship - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1), // NOTE: BOTH because new relationship - isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -605,17 +607,17 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1) )) ); @@ -623,21 +625,21 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, person2, -1, -1, false, false), containsInAnyOrder(List.of( // NOTE: BOTH because new relationship - isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH) + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH) + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -645,17 +647,17 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, orgUnit2, -1, -1, false, false), containsInAnyOrder(List.of( // NOTE: BOTH because new relationship - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1), // NOTE: BOTH because new relationship - isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -674,23 +676,23 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1) )) ); assertThat( relationshipService.findByItem(context, person2, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0) )) ); @@ -707,16 +709,16 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, orgUnit2, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1), + isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -727,54 +729,54 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY), - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH) + isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1) )) ); assertThat( relationshipService.findByItem(context, person2, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY) + isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit2, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH), - isRelationship(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH), - isRelationship(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1), + isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -831,30 +833,30 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPerson, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH), - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH), - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, publication1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH) + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -873,39 +875,39 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPerson, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH), - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH), - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, publication1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH) + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPerson, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) )) ); @@ -916,42 +918,42 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPerson, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH), - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH), - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, publication1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH), - isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH), - isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY) + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH), - isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY) + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) )) ); assertThat( relationshipService.findByItem(context, newPerson, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) )) ); @@ -970,39 +972,39 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPerson, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, publication1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH) + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH) + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, newPerson, -1, -1, false, true), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH), - isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH), - isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 1, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 1, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 1, 0) )) ); @@ -1013,42 +1015,42 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, originalPerson, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, publication1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH) + isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY), - isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH) + isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 1, 0) )) ); assertThat( relationshipService.findByItem(context, newPerson, -1, -1, false, false), containsInAnyOrder(List.of( - isRelationship(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH), - isRelationship(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH), - isRelationship(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 1, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 1, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 1, 0) )) ); From aa77b0a3bb4a16c4114bc6a55d823decf6c57e09 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 23 Mar 2022 23:20:22 +0100 Subject: [PATCH 0770/1254] 88675: WIP: Test virtual metadata on new version --- .../VersioningWithRelationshipsTest.java | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index a1aee64f38..96cf432ab9 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -1340,4 +1340,150 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } } + @Test + public void test_virtualMetadataPreserved() throws Exception { + ////////////////////////////////////////////// + // create a publication and link two people // + ////////////////////////////////////////////// + + Item publication1V1 = ItemBuilder.createItem(context, collection) + .withTitle("publication 1V1") + .withMetadata("dspace", "entity", "type", publicationEntityType.getLabel()) + .build(); + + Item person1V1 = ItemBuilder.createItem(context, collection) + .withTitle("person 1V1") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .withPersonIdentifierFirstName("Donald") + .withPersonIdentifierLastName("Smith") + .build(); + + Item person2V1 = ItemBuilder.createItem(context, collection) + .withTitle("person 2V1") + .withMetadata("dspace", "entity", "type", personEntityType.getLabel()) + .withPersonIdentifierFirstName("Jane") + .withPersonIdentifierLastName("Doe") + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication1V1, person1V1, isAuthorOfPublication) + .build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication1V1, person2V1, isAuthorOfPublication) + .withRightwardValue("Doe, J.") + .build(); + + /////////////////////////////////////////////// + // test dc.contributor.author of publication // + /////////////////////////////////////////////// + + List mdvs1 = itemService.getMetadata( + publication1V1, "dc", "contributor", "author", Item.ANY + ); + assertEquals(2, mdvs1.size()); + + assertTrue(mdvs1.get(0) instanceof RelationshipMetadataValue); + assertEquals("Smith, Donald", mdvs1.get(0).getValue()); + assertEquals(0, mdvs1.get(0).getPlace()); + + assertTrue(mdvs1.get(1) instanceof RelationshipMetadataValue); + assertEquals("Doe, J.", mdvs1.get(1).getValue()); + assertEquals(1, mdvs1.get(1).getPlace()); + + /////////////////////////////////////////////////////// + // create a new version of publication 1 and archive // + /////////////////////////////////////////////////////// + + Item publication1V2 = versioningService.createNewVersion(context, publication1V1).getItem(); + installItemService.installItem(context, workspaceItemService.findByItem(context, publication1V2)); + context.dispatchEvents(); + + //////////////////////////////////// + // create new version of person 1 // + //////////////////////////////////// + + Item person1V2 = versioningService.createNewVersion(context, person1V1).getItem(); + // update "Smith, Donald" to "Smith, D." + itemService.replaceMetadata( + context, person1V2, "person", "givenName", null, null, "D.", + null, -1, 0 + ); + itemService.update(context, person1V2); + + /////////////////////////////////////////////////// + // test dc.contributor.author of old publication // + /////////////////////////////////////////////////// + + List mdvs2 = itemService.getMetadata( + publication1V1, "dc", "contributor", "author", Item.ANY + ); + assertEquals(2, mdvs2.size()); + + assertTrue(mdvs2.get(0) instanceof RelationshipMetadataValue); + assertEquals("Smith, Donald", mdvs2.get(0).getValue()); + assertEquals(0, mdvs2.get(0).getPlace()); + + assertTrue(mdvs2.get(1) instanceof RelationshipMetadataValue); + assertEquals("Doe, J.", mdvs2.get(1).getValue()); + assertEquals(1, mdvs2.get(1).getPlace()); + + /////////////////////////////////////////////////// + // test dc.contributor.author of new publication // + /////////////////////////////////////////////////// + + List mdvs3 = itemService.getMetadata( + publication1V2, "dc", "contributor", "author", Item.ANY + ); + assertEquals(2, mdvs3.size()); + + assertTrue(mdvs3.get(0) instanceof RelationshipMetadataValue); + assertEquals("Smith, Donald", mdvs3.get(0).getValue()); + assertEquals(0, mdvs3.get(0).getPlace()); + + assertTrue(mdvs3.get(1) instanceof RelationshipMetadataValue); + assertEquals("Doe, J.", mdvs3.get(1).getValue()); + assertEquals(1, mdvs3.get(1).getPlace()); + + ///////////////////////////////////// + // archive new version of person 1 // + ///////////////////////////////////// + + installItemService.installItem(context, workspaceItemService.findByItem(context, person1V2)); + context.dispatchEvents(); + + /////////////////////////////////////////////////// + // test dc.contributor.author of old publication // + /////////////////////////////////////////////////// + + List mdvs4 = itemService.getMetadata( + publication1V1, "dc", "contributor", "author", Item.ANY + ); + assertEquals(2, mdvs4.size()); + + assertTrue(mdvs4.get(0) instanceof RelationshipMetadataValue); + assertEquals("Smith, Donald", mdvs4.get(0).getValue()); + assertEquals(0, mdvs4.get(0).getPlace()); + + assertTrue(mdvs4.get(1) instanceof RelationshipMetadataValue); + assertEquals("Doe, J.", mdvs4.get(1).getValue()); + assertEquals(1, mdvs4.get(1).getPlace()); + + /////////////////////////////////////////////////// + // test dc.contributor.author of new publication // + /////////////////////////////////////////////////// + + List mdvs5 = itemService.getMetadata( + publication1V2, "dc", "contributor", "author", Item.ANY + ); + assertEquals(2, mdvs5.size()); + + assertTrue(mdvs5.get(0) instanceof RelationshipMetadataValue); + assertEquals("Smith, D.", mdvs5.get(0).getValue()); + assertEquals(0, mdvs5.get(0).getPlace()); + + assertTrue(mdvs5.get(1) instanceof RelationshipMetadataValue); + assertEquals("Doe, J.", mdvs5.get(1).getValue()); + assertEquals(1, mdvs5.get(1).getPlace()); + } + + // TODO } From cc23600fe96aa09b15e0206ad2cdf94f1815ee84 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 24 Mar 2022 13:48:17 +0100 Subject: [PATCH 0771/1254] [CST-5288] Improved health actuator --- .../authority/AuthoritySolrServiceImpl.java | 2 +- .../configuration/ActuatorConfiguration.java | 38 ++++++++++++++++++- .../app/rest/health/GeoIpHealthIndicator.java | 4 +- .../dspace/app/rest/HealthIndicatorsIT.java | 23 ++++++----- .../link/search/HealthIndicatorMatcher.java | 2 +- dspace/config/dspace.cfg | 1 + 6 files changed, 55 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java index dab8cd5b2e..2fb7a513a6 100644 --- a/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/AuthoritySolrServiceImpl.java @@ -50,7 +50,7 @@ public class AuthoritySolrServiceImpl implements AuthorityIndexingService, Autho */ protected SolrClient solr = null; - protected SolrClient getSolr() + public SolrClient getSolr() throws MalformedURLException, SolrServerException, IOException { if (solr == null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java index cb91ae4152..f2054a7101 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java @@ -7,15 +7,23 @@ */ package org.dspace.app.rest.configuration; +import java.io.IOException; +import java.net.MalformedURLException; import java.util.Arrays; +import org.apache.solr.client.solrj.SolrServerException; import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.health.GeoIpHealthIndicator; +import org.dspace.authority.AuthoritySolrServiceImpl; import org.dspace.discovery.SolrSearchCore; +import org.dspace.statistics.SolrStatisticsCore; +import org.dspace.xoai.services.api.solr.SolrServerResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; +import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.solr.SolrHealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,6 +39,8 @@ import org.springframework.hateoas.Link; @Configuration public class ActuatorConfiguration { + public static final Status UP_WITH_ISSUES_STATUS = new Status("UP_WITH_ISSUES"); + @Autowired private DiscoverableEndpointsService discoverableEndpointsService; @@ -43,11 +53,35 @@ public class ActuatorConfiguration { } @Bean - @ConditionalOnEnabledHealthIndicator("solr") - public SolrHealthIndicator solrHealthIndicator(SolrSearchCore solrSearchCore) { + @ConditionalOnEnabledHealthIndicator("solrSearch") + @ConditionalOnProperty("discovery.search.server") + public SolrHealthIndicator solrSearchCoreHealthIndicator(SolrSearchCore solrSearchCore) { return new SolrHealthIndicator(solrSearchCore.getSolr()); } + @Bean + @ConditionalOnEnabledHealthIndicator("solrStatistics") + @ConditionalOnProperty("solr-statistics.server") + public SolrHealthIndicator solrStatisticsCoreHealthIndicator(SolrStatisticsCore solrStatisticsCore) { + return new SolrHealthIndicator(solrStatisticsCore.getSolr()); + } + + @Bean + @ConditionalOnEnabledHealthIndicator("solrAuthority") + @ConditionalOnProperty("solr.authority.server") + public SolrHealthIndicator solrAuthorityCoreHealthIndicator(AuthoritySolrServiceImpl authoritySolrService) + throws MalformedURLException, SolrServerException, IOException { + return new SolrHealthIndicator(authoritySolrService.getSolr()); + } + + @Bean + @ConditionalOnEnabledHealthIndicator("solrOai") + @ConditionalOnProperty("oai.solr.url") + public SolrHealthIndicator solrOaiCoreHealthIndicator(SolrServerResolver solrServerResolver) + throws SolrServerException { + return new SolrHealthIndicator(solrServerResolver.getServer()); + } + @Bean @ConditionalOnEnabledHealthIndicator("geoIp") public GeoIpHealthIndicator geoIpHealthIndicator() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/GeoIpHealthIndicator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/GeoIpHealthIndicator.java index 5c8ad92dbb..191bb4f1ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/GeoIpHealthIndicator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/health/GeoIpHealthIndicator.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.health; +import static org.dspace.app.rest.configuration.ActuatorConfiguration.UP_WITH_ISSUES_STATUS; + import org.dspace.statistics.GeoIpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.health.AbstractHealthIndicator; @@ -32,7 +34,7 @@ public class GeoIpHealthIndicator extends AbstractHealthIndicator { geoIpService.getDatabaseReader(); builder.up(); } catch (IllegalStateException ex) { - builder.outOfService().withDetail("reason", ex.getMessage()); + builder.status(UP_WITH_ISSUES_STATUS).withDetail("reason", ex.getMessage()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java index c28e172e61..4c63cc1da8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java @@ -7,8 +7,9 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.configuration.ActuatorConfiguration.UP_WITH_ISSUES_STATUS; import static org.dspace.app.rest.link.search.HealthIndicatorMatcher.match; -import static org.dspace.app.rest.link.search.HealthIndicatorMatcher.matchDb; +import static org.dspace.app.rest.link.search.HealthIndicatorMatcher.matchDatabase; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -35,8 +36,8 @@ public class HealthIndicatorsIT extends AbstractControllerIntegrationTest { public void testWithAnonymousUser() throws Exception { getClient().perform(get(HEALTH_PATH)) - .andExpect(status().isServiceUnavailable()) - .andExpect(jsonPath("$.status", is(Status.OUT_OF_SERVICE.getCode()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status", is(UP_WITH_ISSUES_STATUS.getCode()))) .andExpect(jsonPath("$.components").doesNotExist()); } @@ -47,8 +48,8 @@ public class HealthIndicatorsIT extends AbstractControllerIntegrationTest { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get(HEALTH_PATH)) - .andExpect(status().isServiceUnavailable()) - .andExpect(jsonPath("$.status", is(Status.OUT_OF_SERVICE.getCode()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status", is(UP_WITH_ISSUES_STATUS.getCode()))) .andExpect(jsonPath("$.components").doesNotExist()); } @@ -59,12 +60,14 @@ public class HealthIndicatorsIT extends AbstractControllerIntegrationTest { String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get(HEALTH_PATH)) - .andExpect(status().isServiceUnavailable()) - .andExpect(jsonPath("$.status", is(Status.OUT_OF_SERVICE.getCode()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status", is(UP_WITH_ISSUES_STATUS.getCode()))) .andExpect(jsonPath("$.components", allOf( - matchDb(Status.UP), - match("solr", Status.UP, Map.of("status", 0, "detectedPathType", "root")), - match("geoIp", Status.OUT_OF_SERVICE, + matchDatabase(Status.UP), + match("solrSearchCore", Status.UP, Map.of("status", 0, "detectedPathType", "root")), + match("solrOaiCore", Status.UP, Map.of("status", 0, "detectedPathType", "particular core")), + match("solrStatisticsCore", Status.UP, Map.of("status", 0, "detectedPathType", "root")), + match("geoIp", UP_WITH_ISSUES_STATUS, Map.of("reason", "The required 'dbfile' configuration is missing in solr-statistics.cfg!")) ))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/link/search/HealthIndicatorMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/link/search/HealthIndicatorMatcher.java index d2ee1e2c70..5bd0d972d9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/link/search/HealthIndicatorMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/link/search/HealthIndicatorMatcher.java @@ -28,7 +28,7 @@ public final class HealthIndicatorMatcher { } - public static Matcher matchDb(Status status) { + public static Matcher matchDatabase(Status status) { return allOf( hasJsonPath("$.db"), hasJsonPath("$.db.status", is(status.getCode())), diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index fcec7471cc..fed722461a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1578,6 +1578,7 @@ request.item.helpdesk.override = false management.endpoint.health.show-details = when-authorized management.endpoint.health.roles = ADMIN +management.endpoint.health.status.order= down, out-of-service, up-with-issues, up, unknown management.health.ping.enabled = false management.health.diskSpace.enabled = false From 488fffce56e5badf51cb1e04bc02586552100b11 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 24 Mar 2022 12:10:50 -0400 Subject: [PATCH 0772/1254] Update deprecated Velocity configuration keys. #8192 --- dspace-api/pom.xml | 1 - dspace-api/src/main/java/org/dspace/core/Email.java | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 3155790e9d..815a455135 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -927,7 +927,6 @@ org.apache.velocity velocity-engine-core - jar 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 c0d191caf5..6db27c9e4f 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -152,14 +152,14 @@ public class Email { private static final String RESOURCE_REPOSITORY_NAME = "Email"; private static final Properties VELOCITY_PROPERTIES = new Properties(); static { - VELOCITY_PROPERTIES.put(Velocity.RESOURCE_LOADER, "string"); - VELOCITY_PROPERTIES.put("string.resource.loader.description", + VELOCITY_PROPERTIES.put(Velocity.RESOURCE_LOADERS, "string"); + VELOCITY_PROPERTIES.put("resource.loader.string.description", "Velocity StringResource loader"); - VELOCITY_PROPERTIES.put("string.resource.loader.class", + VELOCITY_PROPERTIES.put("resource.loader.string.class", StringResourceLoader.class.getName()); - VELOCITY_PROPERTIES.put("string.resource.loader.repository.name", + VELOCITY_PROPERTIES.put("resource.loader.string.repository.name", RESOURCE_REPOSITORY_NAME); - VELOCITY_PROPERTIES.put("string.resource.loader.repository.static", + VELOCITY_PROPERTIES.put("resource.loader.string.repository.static", "false"); } From 6f78c0b8ba43184aab1166812b97466777e02def Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 24 Mar 2022 12:58:14 -0400 Subject: [PATCH 0773/1254] Update to Velocity 2.3. #8192 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 815a455135..8dd36c9b19 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -1001,7 +1001,7 @@ org.apache.velocity velocity-engine-core - 2.2 + 2.3 org.xmlunit From ba7750a8a097297033268641ed1ac838ba6adeb9 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 25 Mar 2022 11:24:31 +0100 Subject: [PATCH 0774/1254] [CST-5288] Improved info actuator --- dspace/config/dspace.cfg | 4 +--- dspace/config/modules/info-actuator.cfg | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 dspace/config/modules/info-actuator.cfg diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index fed722461a..4c0d04dbff 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1583,9 +1583,6 @@ management.endpoint.health.status.order= down, out-of-service, up-with-issues, u management.health.ping.enabled = false management.health.diskSpace.enabled = false -info.app.name = ${dspace.name} - - #------------------------------------------------------------------# #-------------------MODULE CONFIGURATIONS--------------------------# #------------------------------------------------------------------# @@ -1631,6 +1628,7 @@ include = ${module_dir}/openaire-client.cfg include = ${module_dir}/rdf.cfg include = ${module_dir}/rest.cfg include = ${module_dir}/iiif.cfg +include = ${module_dir}/info-actuator.cfg include = ${module_dir}/solr-statistics.cfg include = ${module_dir}/solrauthority.cfg include = ${module_dir}/spring.cfg diff --git a/dspace/config/modules/info-actuator.cfg b/dspace/config/modules/info-actuator.cfg new file mode 100644 index 0000000000..9d0617ad8f --- /dev/null +++ b/dspace/config/modules/info-actuator.cfg @@ -0,0 +1,19 @@ + +info.app.name = ${dspace.name} +info.app.version = 7.3 + +info.app.dir = ${dspace.dir} +info.app.url = ${dspace.server.url} +info.app.db = ${db.url} +info.app.solr.server = ${solr.server} +info.app.solr.prefix = ${solr.multicorePrefix} +info.app.mail.server = ${mail.server} +info.app.mail.from-address = ${mail.from.address} +info.app.mail.feedback-recipient = ${feedback.recipient} +info.app.mail.mail-admin = ${mail.admin} +info.app.mail.mail-helpdesk = ${mail.helpdesk} +info.app.mail.alert-recipient = ${alert.recipient} + +info.app.cors.allowed-origins = ${rest.cors.allowed-origins} + +info.app.ui.url = ${dspace.ui.url} From a24f75cc082ed7fc4af7d234159e087d4dded562 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 25 Mar 2022 11:44:24 +0100 Subject: [PATCH 0775/1254] [CST-5288] Fixed GeoIpHealthIndicatorTest --- .../org/dspace/app/rest/health/GeoIpHealthIndicatorTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/health/GeoIpHealthIndicatorTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/health/GeoIpHealthIndicatorTest.java index ba454a8f1e..d47493f23c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/health/GeoIpHealthIndicatorTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/health/GeoIpHealthIndicatorTest.java @@ -15,6 +15,7 @@ import static org.mockito.Mockito.when; import java.util.Map; import com.maxmind.geoip2.DatabaseReader; +import org.dspace.app.rest.configuration.ActuatorConfiguration; import org.dspace.statistics.GeoIpService; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,7 +59,7 @@ public class GeoIpHealthIndicatorTest { Health health = geoIpHealthIndicator.health(); - assertThat(health.getStatus(), is(Status.OUT_OF_SERVICE)); + assertThat(health.getStatus(), is(ActuatorConfiguration.UP_WITH_ISSUES_STATUS)); assertThat(health.getDetails(), is(Map.of("reason", "Missing db file"))); } From bf7ef19322c92424ca0c7a52692998900c0015a3 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 25 Mar 2022 13:08:15 +0100 Subject: [PATCH 0776/1254] [CST-5288] Disabled solr oai core health indicator during tests --- dspace-api/src/test/data/dspaceFolder/config/local.cfg | 2 ++ .../src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 3c19a68e9f..bca7fc81bf 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -144,3 +144,5 @@ authentication-ip.Student = 6.6.6.6 useProxies = true proxies.trusted.ipranges = 7.7.7.7 proxies.trusted.include_ui_ip = true + +management.health.solrOai.enabled = false diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java index 4c63cc1da8..8c1c534de1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HealthIndicatorsIT.java @@ -65,7 +65,6 @@ public class HealthIndicatorsIT extends AbstractControllerIntegrationTest { .andExpect(jsonPath("$.components", allOf( matchDatabase(Status.UP), match("solrSearchCore", Status.UP, Map.of("status", 0, "detectedPathType", "root")), - match("solrOaiCore", Status.UP, Map.of("status", 0, "detectedPathType", "particular core")), match("solrStatisticsCore", Status.UP, Map.of("status", 0, "detectedPathType", "root")), match("geoIp", UP_WITH_ISSUES_STATUS, Map.of("reason", "The required 'dbfile' configuration is missing in solr-statistics.cfg!")) From af21d54f97137ec5c3ea831286a04c13cf1a27f1 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 25 Mar 2022 15:42:17 +0100 Subject: [PATCH 0777/1254] [CST-5303] implemented http live import client --- .../scopus/service/LiveImportClient.java | 22 +++++ .../scopus/service/LiveImportClientImpl.java | 94 +++++++++++++++++++ .../config/spring/api/external-services.xml | 2 + 3 files changed, 118 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java new file mode 100644 index 0000000000..50006fd486 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.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.importer.external.scopus.service; + +import java.io.InputStream; +import java.util.Map; + +/** + * Interface for classes that allow to contact LiveImport clients. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public interface LiveImportClient { + + public InputStream executeHttpGetRequest(int timeout, String URL, Map requestParams); + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java new file mode 100644 index 0000000000..f11e2fc4f2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.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.importer.external.scopus.service; + +import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.config.RequestConfig.Builder; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.DefaultProxyRoutePlanner; +import org.apache.log4j.Logger; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link LiveImportClient}. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science dot com) + */ +public class LiveImportClientImpl implements LiveImportClient { + + private static final Logger log = Logger.getLogger(LiveImportClientImpl.class); + + @Autowired + private ConfigurationService configurationService; + + @Override + public InputStream executeHttpGetRequest(int timeout, String URL, Map requestParams) { + HttpGet method = null; + String proxyHost = configurationService.getProperty("http.proxy.host"); + String proxyPort = configurationService.getProperty("http.proxy.port"); + try { + HttpClientBuilder hcBuilder = HttpClients.custom(); + Builder requestConfigBuilder = RequestConfig.custom(); + requestConfigBuilder.setConnectionRequestTimeout(timeout); + + if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) { + HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http"); + DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); + hcBuilder.setRoutePlanner(routePlanner); + } + + method = new HttpGet(getSearchUrl(URL, requestParams)); + method.setConfig(requestConfigBuilder.build()); + + HttpClient client = hcBuilder.build(); + HttpResponse httpResponse = client.execute(method); + if (isNotSuccessfull(httpResponse)) { + throw new RuntimeException(); + } + return httpResponse.getEntity().getContent(); + } catch (Exception e1) { + log.error(e1.getMessage(), e1); + } finally { + if (Objects.nonNull(method)) { + method.releaseConnection(); + } + } + return null; + } + + private String getSearchUrl(String URL, Map requestParams) throws URISyntaxException { + URIBuilder uriBuilder = new URIBuilder(URL); + for (String param : requestParams.keySet()) { + uriBuilder.setParameter(param, requestParams.get(param)); + } + return uriBuilder.toString(); + } + + private boolean isNotSuccessfull(HttpResponse response) { + int statusCode = getStatusCode(response); + return statusCode < 200 || statusCode > 299; + } + + private int getStatusCode(HttpResponse response) { + return response.getStatusLine().getStatusCode(); + } + +} \ No newline at end of file diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 9e28e5d559..7f1295f839 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -5,6 +5,8 @@ + + From 97a9c705790813627fbe31734bd47d6e0f6d9da4 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Fri, 25 Mar 2022 19:43:53 +0100 Subject: [PATCH 0778/1254] 88599: WIP: Fix place algorithm --- .../content/RelationshipServiceImpl.java | 41 ++++++++++++++----- .../DefaultItemVersionProvider.java | 12 +----- .../VersioningWithRelationshipsTest.java | 12 +++--- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index 70333530ca..644253aa9b 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -227,10 +227,10 @@ public class RelationshipServiceImpl implements RelationshipService { Item rightItem = relationship.getRightItem(); List leftRelationships = findByItemAndRelationshipType( - context, leftItem, relationship.getRelationshipType(), true + context, leftItem, relationship.getRelationshipType(), true, -1, -1, false ); List rightRelationships = findByItemAndRelationshipType( - context, rightItem, relationship.getRelationshipType(), false + context, rightItem, relationship.getRelationshipType(), false, -1, -1, false ); // These relationships are only deleted from the temporary lists in case they're present in them so that we can @@ -250,6 +250,7 @@ public class RelationshipServiceImpl implements RelationshipService { int oldLeftPlace = relationship.getLeftPlace(); int oldRightPlace = relationship.getRightPlace(); + boolean movedUpLeft = resolveRelationshipPlace( relationship, true, leftRelationships, leftMetadata, oldLeftPlace, newLeftPlace ); @@ -259,14 +260,18 @@ public class RelationshipServiceImpl implements RelationshipService { context.turnOffAuthorisationSystem(); - shiftSiblings( - relationship, true, oldLeftPlace, movedUpLeft, insertLeft, deletedFromLeft, - leftRelationships, leftMetadata - ); - shiftSiblings( - relationship, false, oldRightPlace, movedUpRight, insertRight, deletedFromRight, - rightRelationships, rightMetadata - ); + if (otherSideIsLatest(true, relationship.getLatestVersionStatus())) { + shiftSiblings( + relationship, true, oldLeftPlace, movedUpLeft, insertLeft, deletedFromLeft, + leftRelationships, leftMetadata + ); + } + if (otherSideIsLatest(false, relationship.getLatestVersionStatus())) { + shiftSiblings( + relationship, false, oldRightPlace, movedUpRight, insertRight, deletedFromRight, + rightRelationships, rightMetadata + ); + } updateItem(context, leftItem); updateItem(context, rightItem); @@ -474,6 +479,22 @@ public class RelationshipServiceImpl implements RelationshipService { } } + /** + * Given a latest version status, check if the other side is "latest". + * If we look from the left, this implies BOTH and RIGHT_ONLY return true. + * If we look from the right, this implies BOTH and LEFT_ONLY return true. + * @param isLeft whether we should look from the left or right side. + * @param latestVersionStatus the latest version status. + * @return true if the other side has "latest" status, false otherwise. + */ + private boolean otherSideIsLatest(boolean isLeft, LatestVersionStatus latestVersionStatus) { + if (latestVersionStatus == LatestVersionStatus.BOTH) { + return true; + } + + return latestVersionStatus == (isLeft ? LatestVersionStatus.RIGHT_ONLY : LatestVersionStatus.LEFT_ONLY); + } + private int getPlace(Relationship relationship, boolean isLeft) { if (isLeft) { return relationship.getLeftPlace(); diff --git a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java index e12acfca33..d4590ae24e 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/DefaultItemVersionProvider.java @@ -148,12 +148,8 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen newItem, // new item oldRelationship.getRightItem(), oldRelationship.getRelationshipType(), - // NOTE: on the side of the new version, we start with an empty list of relationships - // => insert at the same position as the ancestral relationship oldRelationship.getLeftPlace(), - // NOTE: on the opposite side of the new version, the ancestral relationship already takes our - // desired place => insert AFTER the ancestral relationship - oldRelationship.getRightPlace() + 1, + oldRelationship.getRightPlace(), oldRelationship.getLeftwardValue(), oldRelationship.getRightwardValue(), Relationship.LatestVersionStatus.RIGHT_ONLY // only mark the opposite side as "latest" for now @@ -165,11 +161,7 @@ public class DefaultItemVersionProvider extends AbstractVersionProvider implemen oldRelationship.getLeftItem(), newItem, // new item oldRelationship.getRelationshipType(), - // NOTE: on the opposite side of the new version, the ancestral relationship already takes our - // desired place => insert AFTER the ancestral relationship - oldRelationship.getLeftPlace() + 1, - // NOTE: on the side of the new version, we start with an empty list of relationships - // => insert at the same position as the ancestral relationship + oldRelationship.getLeftPlace(), oldRelationship.getRightPlace(), oldRelationship.getLeftwardValue(), oldRelationship.getRightwardValue(), diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index 96cf432ab9..dc4905dc29 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -156,7 +156,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_createNewVersionOfItemOnLeftSideOfRelationships() throws Exception { + public void test_createNewVersionOfItemOnLeftSideOfRelationships() throws Exception { // TODO /////////////////////////////////////////////// // create a publication with 3 relationships // /////////////////////////////////////////////// @@ -430,7 +430,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_createNewVersionOfItemAndModifyRelationships() throws Exception { + public void test_createNewVersionOfItemAndModifyRelationships() throws Exception { // TODO /////////////////////////////////////////////// // create a publication with 3 relationships // /////////////////////////////////////////////// @@ -792,7 +792,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_createNewVersionOfItemOnRightSideOfRelationships() throws Exception { + public void test_createNewVersionOfItemOnRightSideOfRelationships() throws Exception { // TODO ////////////////////////////////////////// // create a person with 3 relationships // ////////////////////////////////////////// @@ -1066,7 +1066,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_createNewVersionOfItemAndVerifyMetadataOrder() throws Exception { + public void test_createNewVersionOfItemAndVerifyMetadataOrder() throws Exception { // TODO ///////////////////////////////////////// // create a publication with 6 authors // ///////////////////////////////////////// @@ -1341,7 +1341,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_virtualMetadataPreserved() throws Exception { + public void test_virtualMetadataPreserved() throws Exception { // TODO ////////////////////////////////////////////// // create a publication and link two people // ////////////////////////////////////////////// @@ -1477,7 +1477,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertEquals(2, mdvs5.size()); assertTrue(mdvs5.get(0) instanceof RelationshipMetadataValue); - assertEquals("Smith, D.", mdvs5.get(0).getValue()); + assertEquals("Smith, D.", mdvs5.get(0).getValue());// TODO fix assertEquals(0, mdvs5.get(0).getPlace()); assertTrue(mdvs5.get(1) instanceof RelationshipMetadataValue); From c4c8a4bc07dd08c49dfd63a8c70ced39dd1a7972 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 30 Mar 2022 02:15:32 +0200 Subject: [PATCH 0779/1254] 88599: Update expected places --- .../VersioningWithRelationshipsTest.java | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java index dc4905dc29..f375917fa9 100644 --- a/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/VersioningWithRelationshipsTest.java @@ -156,7 +156,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_createNewVersionOfItemOnLeftSideOfRelationships() throws Exception { // TODO + public void test_createNewVersionOfItemOnLeftSideOfRelationships() throws Exception { /////////////////////////////////////////////// // create a publication with 3 relationships // /////////////////////////////////////////////// @@ -269,9 +269,9 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1), - isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 1), - isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); @@ -292,7 +292,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); @@ -300,7 +300,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), - isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 1) + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); @@ -308,16 +308,16 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0), - isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 1) + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1), - isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 1), - isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); @@ -345,30 +345,30 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, person1, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1), - isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 1), - isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -389,7 +389,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -397,7 +397,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(originalPublication, isProjectOfPublication, project1, LatestVersionStatus.RIGHT_ONLY, 0, 0), - isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -405,16 +405,16 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(originalPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.RIGHT_ONLY, 0, 0), - isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1), - isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 1), - isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), + isRel(newPublication, isProjectOfPublication, project1, LatestVersionStatus.BOTH, 0, 0), + isRel(newPublication, isOrgUnitOfPublication, orgUnit1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -430,7 +430,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_createNewVersionOfItemAndModifyRelationships() throws Exception { // TODO + public void test_createNewVersionOfItemAndModifyRelationships() throws Exception { /////////////////////////////////////////////// // create a publication with 3 relationships // /////////////////////////////////////////////// @@ -593,7 +593,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), // NOTE: BOTH because new relationship isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0), isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) @@ -617,7 +617,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0) )) ); @@ -654,7 +654,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 1), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), // NOTE: BOTH because new relationship isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0), isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) @@ -685,7 +685,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, person1, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -716,7 +716,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0), isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) @@ -739,7 +739,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, person1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(originalPublication, isAuthorOfPublication, person1, LatestVersionStatus.RIGHT_ONLY, 0, 0), - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1) + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -774,7 +774,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, newPublication, -1, -1, false, false), containsInAnyOrder(List.of( - isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 1), + isRel(newPublication, isAuthorOfPublication, person1, LatestVersionStatus.BOTH, 0, 0), isRel(newPublication, isAuthorOfPublication, person2, LatestVersionStatus.BOTH, 1, 0), isRel(newPublication, isOrgUnitOfPublication, orgUnit2, LatestVersionStatus.BOTH, 0, 0) )) @@ -792,7 +792,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_createNewVersionOfItemOnRightSideOfRelationships() throws Exception { // TODO + public void test_createNewVersionOfItemOnRightSideOfRelationships() throws Exception { ////////////////////////////////////////// // create a person with 3 relationships // ////////////////////////////////////////// @@ -905,9 +905,9 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, newPerson, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0), - isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0), - isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0) )) ); @@ -928,7 +928,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, publication1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.BOTH, 0, 0), - isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0) )) ); @@ -936,7 +936,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.BOTH, 0, 0), - isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0) )) ); @@ -944,16 +944,16 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.BOTH, 0, 0), - isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPerson, -1, -1, false, false), containsInAnyOrder(List.of( - isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0), - isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0), - isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 1, 0) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.LEFT_ONLY, 0, 0) )) ); @@ -981,30 +981,30 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith assertThat( relationshipService.findByItem(context, publication1, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 1, 0) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, project1, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 1, 0) + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, orgUnit1, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 1, 0) + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPerson, -1, -1, false, true), containsInAnyOrder(List.of( - isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 1, 0), - isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 1, 0), - isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 1, 0) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -1025,7 +1025,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, publication1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(publication1, isAuthorOfPublication, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), - isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 1, 0) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -1033,7 +1033,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, project1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(project1, isMemberOfProject, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), - isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 1, 0) + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -1041,16 +1041,16 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith relationshipService.findByItem(context, orgUnit1, -1, -1, false, false), containsInAnyOrder(List.of( isRel(orgUnit1, isMemberOfOrgUnit, originalPerson, LatestVersionStatus.LEFT_ONLY, 0, 0), - isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 1, 0) + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 0, 0) )) ); assertThat( relationshipService.findByItem(context, newPerson, -1, -1, false, false), containsInAnyOrder(List.of( - isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 1, 0), - isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 1, 0), - isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 1, 0) + isRel(publication1, isAuthorOfPublication, newPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(project1, isMemberOfProject, newPerson, LatestVersionStatus.BOTH, 0, 0), + isRel(orgUnit1, isMemberOfOrgUnit, newPerson, LatestVersionStatus.BOTH, 0, 0) )) ); @@ -1066,7 +1066,7 @@ public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWith } @Test - public void test_createNewVersionOfItemAndVerifyMetadataOrder() throws Exception { // TODO + public void test_createNewVersionOfItemAndVerifyMetadataOrder() throws Exception { ///////////////////////////////////////// // create a publication with 6 authors // ///////////////////////////////////////// From 6a1cdd6e2db4244e958e91ba210f37e2f2722168 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 30 Mar 2022 12:48:54 +0200 Subject: [PATCH 0780/1254] [CST-5306] Migrate Researcher Profile (REST) --- .../exception/ResourceConflictException.java | 39 + .../dspace/app/profile/ResearcherProfile.java | 85 ++ .../profile/ResearcherProfileServiceImpl.java | 312 +++++ .../AfterResearcherProfileCreationAction.java | 35 + .../service/ResearcherProfileService.java | 86 ++ .../authorize/AuthorizeServiceImpl.java | 4 + .../authorize/service/AuthorizeService.java | 1 + .../org/dspace/content/ItemServiceImpl.java | 48 + .../content/authority/EPersonAuthority.java | 93 ++ .../dspace/content/service/ItemService.java | 39 +- .../test/data/dspaceFolder/config/local.cfg | 2 +- .../org/dspace/app/matcher/LambdaMatcher.java | 55 + .../app/matcher/MetadataValueMatcher.java | 96 ++ .../java/org/dspace/builder/ItemBuilder.java | 18 + .../converter/ResearcherProfileConverter.java | 94 ++ .../DSpaceApiExceptionControllerAdvice.java | 17 + .../app/rest/login/PostLoggedInAction.java | 27 + .../rest/login/impl/LoginEventFireAction.java | 46 + .../impl/ResearcherProfileAutomaticClaim.java | 129 +++ .../app/rest/model/ResearcherProfileRest.java | 134 +++ .../hateoas/ResearcherProfileResource.java | 29 + ...esearcherProfileEPersonLinkRepository.java | 78 ++ .../ResearcherProfileItemLinkRepository.java | 67 ++ .../ResearcherProfileRestRepository.java | 189 +++ ...earcherProfileVisibleReplaceOperation.java | 67 ++ .../EPersonRestAuthenticationProvider.java | 18 + ...rProfileRestPermissionEvaluatorPlugin.java | 74 ++ .../ResearcherProfileRestRepositoryIT.java | 1032 +++++++++++++++++ .../app/rest/matcher/MetadataMatcher.java | 17 + dspace/config/modules/authority.cfg | 15 + dspace/config/registries/dspace-types.xml | 8 + dspace/config/spring/api/core-services.xml | 2 + .../spring/rest/post-logged-in-actions.xml | 15 + 33 files changed, 2969 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/app/profile/ResearcherProfile.java create mode 100644 dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/app/profile/service/AfterResearcherProfileCreationAction.java create mode 100644 dspace-api/src/main/java/org/dspace/app/profile/service/ResearcherProfileService.java create mode 100644 dspace-api/src/main/java/org/dspace/content/authority/EPersonAuthority.java create mode 100644 dspace-api/src/test/java/org/dspace/app/matcher/LambdaMatcher.java create mode 100644 dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/login/PostLoggedInAction.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/LoginEventFireAction.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ResearcherProfileResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java create mode 100644 dspace/config/modules/authority.cfg create mode 100644 dspace/config/spring/rest/post-logged-in-actions.xml 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 0000000000..14e415aeb8 --- /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 Luca Giamminonni (luca.giamminonni at 4science.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/app/profile/ResearcherProfile.java b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java new file mode 100644 index 0000000000..59f4a0cfff --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java @@ -0,0 +1,85 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.profile; + +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.util.UUIDUtils; +import org.springframework.util.Assert; + +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.dspace.core.Constants.READ; +import static org.dspace.eperson.Group.ANONYMOUS; + +/** + * Object representing a Researcher Profile. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class ResearcherProfile { + + private final Item item; + + private final MetadataValue dspaceObjectOwner; + + /** + * Create a new ResearcherProfile object from the given item. + * + * @param item the profile item + * @throws IllegalArgumentException if the given item has not a dspace.object.owner + * metadata with a valid authority + */ + public ResearcherProfile(Item item) { + Assert.notNull(item, "A researcher profile requires an item"); + this.item = item; + this.dspaceObjectOwner = getDspaceObjectOwnerMetadata(item); + } + + public UUID getId() { + return UUIDUtils.fromString(dspaceObjectOwner.getAuthority()); + } + + public String getFullName() { + return dspaceObjectOwner.getValue(); + } + + public boolean isVisible() { + return item.getResourcePolicies().stream() + .filter(policy -> policy.getGroup() != null) + .anyMatch(policy -> READ == policy.getAction() && ANONYMOUS.equals(policy.getGroup().getName())); + } + + public Item getItem() { + return item; + } + +// public Optional getOrcid() { +// return getMetadataValue(item, "person.identifier.orcid") +// .map(metadataValue -> metadataValue.getValue()); +// } + + private MetadataValue getDspaceObjectOwnerMetadata(Item item) { + return getMetadataValue(item, "dspace.object.owner") + .filter(metadata -> UUIDUtils.fromString(metadata.getAuthority()) != null) + .orElseThrow(() -> new IllegalArgumentException("A profile item must have a valid dspace.object.owner metadata")); + } + + private Optional getMetadataValue(Item item, String metadataField) { + return getMetadataValues(item, metadataField).findFirst(); + } + + private Stream getMetadataValues(Item item, String metadataField) { + return item.getMetadata().stream() + .filter(metadata -> metadataField.equals(metadata.getMetadataField().toString('.'))); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java new file mode 100644 index 0000000000..8b9964972c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java @@ -0,0 +1,312 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.profile; + +import static org.dspace.content.authority.Choices.CF_ACCEPTED; +import static org.dspace.core.Constants.READ; +import static org.dspace.eperson.Group.ANONYMOUS; + +import java.io.IOException; +import java.net.URI; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.annotation.PostConstruct; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.dspace.app.exception.ResourceConflictException; +import org.dspace.app.profile.service.AfterResearcherProfileCreationAction; +import org.dspace.app.profile.service.ResearcherProfileService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.InstallItemService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.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.discovery.indexobject.IndexableCollection; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; +import org.dspace.util.UUIDUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; + +/** + * Implementation of {@link ResearcherProfileService}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class ResearcherProfileServiceImpl implements ResearcherProfileService { + + private static Logger log = LoggerFactory.getLogger(ResearcherProfileServiceImpl.class); + + @Autowired + private ItemService itemService; + + @Autowired + private WorkspaceItemService workspaceItemService; + + @Autowired + private InstallItemService installItemService; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private CollectionService collectionService; + + @Autowired + private SearchService searchService; + + @Autowired + private GroupService groupService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired(required = false) + private List afterCreationActions; + + @PostConstruct + public void postConstruct() { + + if (afterCreationActions == null) { + afterCreationActions = Collections.emptyList(); + } + + } + + @Override + public ResearcherProfile findById(Context context, UUID id) throws SQLException, AuthorizeException { + Assert.notNull(id, "An id must be provided to find a researcher profile"); + + Item profileItem = findResearcherProfileItemById(context, id); + if (profileItem == null) { + return null; + } + + return new ResearcherProfile(profileItem); + } + + @Override + public ResearcherProfile createAndReturn(Context context, EPerson ePerson) + throws AuthorizeException, SQLException, SearchServiceException { + + Item profileItem = findResearcherProfileItemById(context, ePerson.getID()); + if (profileItem != null) { + ResearcherProfile profile = new ResearcherProfile(profileItem); + throw new ResourceConflictException("A profile is already linked to the provided User", profile); + } + + Collection collection = findProfileCollection(context); + if (collection == null) { + throw new IllegalStateException("No collection found for researcher profiles"); + } + + context.turnOffAuthorisationSystem(); + Item item = createProfileItem(context, ePerson, collection); + context.restoreAuthSystemState(); + + ResearcherProfile researcherProfile = new ResearcherProfile(item); + + for (AfterResearcherProfileCreationAction afterCreationAction : afterCreationActions) { + afterCreationAction.perform(context, researcherProfile, ePerson); + } + + return researcherProfile; + } + + @Override + public void deleteById(Context context, UUID id) throws SQLException, AuthorizeException { + Assert.notNull(id, "An id must be provided to find a researcher profile"); + + Item profileItem = findResearcherProfileItemById(context, id); + if (profileItem == null) { + return; + } + + if (isHardDeleteEnabled()) { + deleteItem(context, profileItem); + } else { + removeDspaceObjectOwnerMetadata(context, profileItem); + } + + } + + @Override + public void changeVisibility(Context context, ResearcherProfile profile, boolean visible) + throws AuthorizeException, SQLException { + + if (profile.isVisible() == visible) { + return; + } + + Item item = profile.getItem(); + Group anonymous = groupService.findByName(context, ANONYMOUS); + + if (visible) { + authorizeService.addPolicy(context, item, READ, anonymous); + } else { + authorizeService.removeGroupPolicies(context, item, anonymous); + } + + } + + @Override + public ResearcherProfile claim(final Context context, final EPerson ePerson, final URI uri) + throws SQLException, AuthorizeException, SearchServiceException { + Item profileItem = findResearcherProfileItemById(context, ePerson.getID()); + if (profileItem != null) { + ResearcherProfile profile = new ResearcherProfile(profileItem); + throw new ResourceConflictException("A profile is already linked to the provided User", profile); + } + + Collection collection = findProfileCollection(context); + if (collection == null) { + throw new IllegalStateException("No collection found for researcher profiles"); + } + + final String path = uri.getPath(); + final UUID uuid = UUIDUtils.fromString(path.substring(path.lastIndexOf("/") + 1 )); + Item item = itemService.find(context, uuid); + if (Objects.isNull(item) || !item.isArchived() || item.isWithdrawn() || notClaimableEntityType(item)) { + throw new IllegalArgumentException("Provided uri does not represent a valid Item to be claimed"); + } + final String existingOwner = itemService.getMetadataFirstValue(item, "dspace", "object", + "owner", null); + + if (StringUtils.isNotBlank(existingOwner)) { + throw new IllegalArgumentException("Item with provided uri has already an owner"); + } + + context.turnOffAuthorisationSystem(); + itemService.addMetadata(context, item, "dspace", "object", "owner", null, ePerson.getName(), + ePerson.getID().toString(), CF_ACCEPTED); + + context.restoreAuthSystemState(); + return new ResearcherProfile(item); + } + + private boolean notClaimableEntityType(final Item item) { + final String entityType = itemService.getEntityType(item); + return Arrays.stream(configurationService.getArrayProperty("claimable.entityType")) + .noneMatch(entityType::equals); + } + + private Item findResearcherProfileItemById(Context context, UUID id) throws SQLException, AuthorizeException { + + String profileType = getProfileType(); + + Iterator items = itemService.findByAuthorityValue(context, "dspace", "object", "owner", id.toString()); + while (items.hasNext()) { + Item item = items.next(); + if (hasEntityTypeMetadataEqualsTo(item, profileType)) { + return item; + } + } + return null; + } + + @SuppressWarnings("rawtypes") + private Collection findProfileCollection(Context context) throws SQLException, SearchServiceException { + UUID uuid = UUIDUtils.fromString(configurationService.getProperty("researcher-profile.collection.uuid")); + if (uuid != null) { + return collectionService.find(context, uuid); + } + + String profileType = getProfileType(); + + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); + discoverQuery.addFilterQueries("dspace.entity.type:" + profileType); + + DiscoverResult discoverResult = searchService.search(context, discoverQuery); + List indexableObjects = discoverResult.getIndexableObjects(); + + if (CollectionUtils.isEmpty(indexableObjects)) { + return null; + } + + if (indexableObjects.size() > 1) { + log.warn("Multiple " + profileType + " type collections were found during profile creation"); + return null; + } + + return (Collection) indexableObjects.get(0).getIndexedObject(); + } + + private Item createProfileItem(Context context, EPerson ePerson, Collection collection) + throws AuthorizeException, SQLException { + + String id = ePerson.getID().toString(); + + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, true); + Item item = workspaceItem.getItem(); + itemService.addMetadata(context, item, "dc", "title", null, null, ePerson.getFullName()); + itemService.addMetadata(context, item, "dspace", "object", "owner", null, ePerson.getFullName(), id, CF_ACCEPTED); + + item = installItemService.installItem(context, workspaceItem); + + Group anonymous = groupService.findByName(context, ANONYMOUS); + authorizeService.removeGroupPolicies(context, item, anonymous); + authorizeService.addPolicy(context, item, READ, ePerson); + + return item; + } + + private boolean hasEntityTypeMetadataEqualsTo(Item item, String entityType) { + return item.getMetadata().stream().anyMatch(metadataValue -> { + return "dspace.entity.type".equals(metadataValue.getMetadataField().toString('.')) && + entityType.equals(metadataValue.getValue()); + }); + } + + private boolean isHardDeleteEnabled() { + return configurationService.getBooleanProperty("researcher-profile.hard-delete.enabled"); + } + + private void removeDspaceObjectOwnerMetadata(Context context, Item profileItem) throws SQLException { + List metadata = itemService.getMetadata(profileItem, "dspace", "object", "owner", Item.ANY); + itemService.removeMetadataValues(context, profileItem, metadata); + } + + private void deleteItem(Context context, Item profileItem) throws SQLException, AuthorizeException { + try { + context.turnOffAuthorisationSystem(); + itemService.delete(context, profileItem); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + context.restoreAuthSystemState(); + } + } + + private String getProfileType() { + return configurationService.getProperty("researcher-profile.type", "Person"); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/app/profile/service/AfterResearcherProfileCreationAction.java b/dspace-api/src/main/java/org/dspace/app/profile/service/AfterResearcherProfileCreationAction.java new file mode 100644 index 0000000000..7bf5c97aa8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/profile/service/AfterResearcherProfileCreationAction.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.profile.service; + +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +import java.sql.SQLException; + +/** + * Interface to mark classes that allow to perform additional logic on created + * researcher profile. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public interface AfterResearcherProfileCreationAction { + + /** + * Perform some actions on the given researcher profile and returns the updated + * profile. + * + * @param context the DSpace context + * @param researcherProfile the created researcher profile + * @param owner the EPerson that is owner of the given profile + * @throws SQLException if a SQL error occurs + */ + void perform(Context context, ResearcherProfile researcherProfile, EPerson owner) throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/app/profile/service/ResearcherProfileService.java b/dspace-api/src/main/java/org/dspace/app/profile/service/ResearcherProfileService.java new file mode 100644 index 0000000000..0d565f0754 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/profile/service/ResearcherProfileService.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.profile.service; + +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.EPerson; + +import java.net.URI; +import java.sql.SQLException; +import java.util.UUID; + +/** + * Service interface class for the {@link ResearcherProfile} object. The + * implementation of this class is responsible for all business logic calls for + * the {@link ResearcherProfile} object. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public interface ResearcherProfileService { + + /** + * Find the ResearcherProfile by UUID. + * + * @param context the relevant DSpace Context. + * @param id the ResearcherProfile id + * @return the found ResearcherProfile + * @throws SQLException + * @throws AuthorizeException + */ + public ResearcherProfile findById(Context context, UUID id) throws SQLException, AuthorizeException; + + /** + * Create a new researcher profile for the given ePerson. + * + * @param context the relevant DSpace Context. + * @param ePerson the ePerson + * @return the created profile + * @throws SQLException + * @throws AuthorizeException + * @throws SearchServiceException + */ + public ResearcherProfile createAndReturn(Context context, EPerson ePerson) + throws AuthorizeException, SQLException, SearchServiceException; + + /** + * Removes the association between the researcher profile and eperson related to + * the input uuid. + * + * @param context the relevant DSpace Context. + * @param id the researcher profile id + * @throws AuthorizeException + * @throws SQLException + */ + public void deleteById(Context context, UUID id) throws SQLException, AuthorizeException; + + /** + * Changes the visibility of the given profile using the given new visible value + * + * @param context the relevant DSpace Context. + * @param profile the researcher profile to update + * @param visible the visible value to set + * @throws SQLException + * @throws AuthorizeException + */ + public void changeVisibility(Context context, ResearcherProfile profile, boolean visible) + throws AuthorizeException, SQLException; + + /** + * Claims and links an eperson to an existing DSpaceObject + * @param context the relevant DSpace Context. + * @param ePerson the ePerson + * @param uri uri of existing DSpaceObject to be linked to the eperson + * @return + */ + ResearcherProfile claim(Context context, EPerson ePerson, URI uri) + throws SQLException, AuthorizeException, SearchServiceException; +} 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 919e82f14f..57300e8e18 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -959,4 +959,8 @@ public class AuthorizeServiceImpl implements AuthorizeService { return query + " AND "; } } + @Override + public boolean isPartOfTheGroup(Context c, String egroup) throws SQLException { + return false; + } } 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 9f6171a220..65bf0f31f9 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 @@ -592,4 +592,5 @@ public interface AuthorizeService { */ long countAdminAuthorizedCollection(Context context, String query) throws SearchServiceException, SQLException; + public boolean isPartOfTheGroup(Context c, String egroup) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index dbde9745fb..1da8a3a4eb 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1131,6 +1131,50 @@ prevent the generation of resource policy entry values with null dspace_object a return !(hasCustomPolicy && isAnonimousGroup && datesAreNull); } + /** + * Returns an iterator of Items possessing the passed metadata field, or only + * those matching the passed value, if value is not Item.ANY + * + * @param context DSpace context object + * @param schema metadata field schema + * @param element metadata field element + * @param qualifier metadata field qualifier + * @param value field value or Item.ANY to match any value + * @return an iterator over the items matching that authority value + * @throws SQLException if database error + * An exception that provides information on a database access error or other errors. + * @throws AuthorizeException if authorization error + * Exception indicating the current user of the context does not have permission + * to perform a particular action. + */ + @Override + public Iterator findArchivedByMetadataField(Context context, + String schema, String element, String qualifier, String value) + throws SQLException, AuthorizeException { + MetadataSchema mds = metadataSchemaService.find(context, schema); + if (mds == null) { + throw new IllegalArgumentException("No such metadata schema: " + schema); + } + MetadataField mdf = metadataFieldService.findByElement(context, mds, element, qualifier); + if (mdf == null) { + throw new IllegalArgumentException( + "No such metadata field: schema=" + schema + ", element=" + element + ", qualifier=" + qualifier); + } + + if (Item.ANY.equals(value)) { + return itemDAO.findByMetadataField(context, mdf, null, true); + } else { + return itemDAO.findByMetadataField(context, mdf, value, true); + } + } + + @Override + public Iterator findArchivedByMetadataField(Context context, String metadataField, String value) + throws SQLException, AuthorizeException { + String[] mdValueByField = getMDValueByField(metadataField); + return findArchivedByMetadataField(context, mdValueByField[0], mdValueByField[1], mdValueByField[2], value); + } + /** * Returns an iterator of Items possessing the passed metadata field, or only * those matching the passed value, if value is not Item.ANY @@ -1535,5 +1579,9 @@ prevent the generation of resource policy entry values with null dspace_object a .stream().findFirst().orElse(null); } + @Override + public String getEntityType(Item item) { + return getMetadataFirstValue(item, new MetadataFieldName("dspace.entity.type"), Item.ANY); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/EPersonAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/EPersonAuthority.java new file mode 100644 index 0000000000..7a1510d721 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/authority/EPersonAuthority.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.authority; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.apache.log4j.Logger; +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.util.UUIDUtils; + +/** + * + * @author Mykhaylo Boychuk (4science.it) + */ +public class EPersonAuthority implements ChoiceAuthority { + private static final Logger log = Logger.getLogger(EPersonAuthority.class); + + /** + * the name assigned to the specific instance by the PluginService, @see + * {@link NameAwarePlugin} + **/ + private String authorityName; + + private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + @Override + public Choices getBestMatch(String text, String locale) { + return getMatches(text, 0, 2, locale); + } + + @Override + public Choices getMatches(String text, int start, int limit, String locale) { + Context context = null; + if (limit <= 0) { + limit = 20; + } + context = new Context(); + List ePersons = null; + try { + ePersons = ePersonService.search(context, text, start, limit); + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + List choiceList = new ArrayList(); + for (EPerson eperson : ePersons) { + choiceList.add(new Choice(eperson.getID().toString(), eperson.getFullName(), eperson.getFullName())); + } + Choice[] results = new Choice[choiceList.size()]; + results = choiceList.toArray(results); + return new Choices(results, start, ePersons.size(), Choices.CF_AMBIGUOUS, ePersons.size() > (start + limit), 0); + } + + @Override + public String getLabel(String key, String locale) { + + UUID uuid = UUIDUtils.fromString(key); + if (uuid == null) { + return null; + } + + Context context = new Context(); + try { + EPerson ePerson = ePersonService.find(context, uuid); + return ePerson != null ? ePerson.getFullName() : null; + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + + } + + @Override + public String getPluginInstanceName() { + return authorityName; + } + + @Override + public void setPluginInstanceName(String name) { + this.authorityName = name; + } +} \ No newline at end of file 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 d5e2f67767..2d54680299 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 @@ -579,6 +579,36 @@ public interface ItemService */ public boolean canCreateNewVersion(Context context, Item item) throws SQLException; + /** + * Returns an iterator of in archive items possessing the passed metadata field, or only + * those matching the passed value, if value is not Item.ANY + * + * @param context DSpace context object + * @param schema metadata field schema + * @param element metadata field element + * @param qualifier metadata field qualifier + * @param value field value or Item.ANY to match any value + * @return an iterator over the items matching that authority value + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public Iterator findArchivedByMetadataField(Context context, String schema, String element, + String qualifier, String value) throws SQLException, AuthorizeException; + + /** + * Returns an iterator of in archive items possessing the passed metadata field, or only + * those matching the passed value, if value is not Item.ANY + * + * @param context DSpace context object + * @param metadataField metadata + * @param value field value or Item.ANY to match any value + * @return an iterator over the items matching that authority value + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public Iterator findArchivedByMetadataField(Context context, String metadataField, String value) + throws SQLException, AuthorizeException; + /** * Returns an iterator of Items possessing the passed metadata field, or only * those matching the passed value, if value is not Item.ANY @@ -618,7 +648,7 @@ public interface ItemService */ public Iterator findByAuthorityValue(Context context, String schema, String element, String qualifier, String value) - throws SQLException, AuthorizeException, IOException; + throws SQLException, AuthorizeException; public Iterator findByMetadataFieldAuthority(Context context, String mdString, String authority) @@ -782,5 +812,12 @@ public interface ItemService */ public List getMetadata(Item item, String schema, String element, String qualifier, String lang, boolean enableVirtualMetadata); + /** + * Returns the item's entity type, if any. + * + * @param item the item + * @return the entity type as string, if any + */ + public String getEntityType(Item item); } diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 3c19a68e9f..11a344fa0d 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -20,7 +20,7 @@ # For example, including "dspace.dir" in this local.cfg will override the # default value of "dspace.dir" in the dspace.cfg file. # - +researcher-profile.type = Person ########################## # SERVER CONFIGURATION # ########################## diff --git a/dspace-api/src/test/java/org/dspace/app/matcher/LambdaMatcher.java b/dspace-api/src/test/java/org/dspace/app/matcher/LambdaMatcher.java new file mode 100644 index 0000000000..641aee5704 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/matcher/LambdaMatcher.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.matcher; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +import java.util.function.Predicate; + +/** + * Matcher based on an {@link Predicate}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * @param the type of the instance to match + */ +public class LambdaMatcher extends BaseMatcher { + + private final Predicate matcher; + private final String description; + + public static LambdaMatcher matches(Predicate matcher) { + return new LambdaMatcher(matcher, "Matches the given predicate"); + } + + public static LambdaMatcher matches(Predicate matcher, String description) { + return new LambdaMatcher(matcher, description); + } + + public static Matcher> has(Predicate matcher) { + return Matchers.hasItem(matches(matcher)); + } + + private LambdaMatcher(Predicate matcher, String description) { + this.matcher = matcher; + this.description = description; + } + + @Override + @SuppressWarnings("unchecked") + public boolean matches(Object argument) { + return matcher.test((T) argument); + } + + @Override + public void describeTo(Description description) { + description.appendText(this.description); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java b/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java new file mode 100644 index 0000000000..eb2158a9ce --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java @@ -0,0 +1,96 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.matcher; + +import org.dspace.content.MetadataValue; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +import java.util.Objects; + +/** + * Implementation of {@link org.hamcrest.Matcher} to match a MetadataValue by + * all its attributes. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class MetadataValueMatcher extends TypeSafeMatcher { + + private String field; + + private String value; + + private String language; + + private String authority; + + private Integer place; + + private Integer confidence; + + private MetadataValueMatcher(String field, String value, String language, String authority, Integer place, + Integer confidence) { + + this.field = field; + this.value = value; + this.language = language; + this.authority = authority; + this.place = place; + this.confidence = confidence; + + } + + @Override + public void describeTo(Description description) { + description.appendText("MetadataValue with the following attributes [field=" + field + ", value=" + + value + ", language=" + language + ", authority=" + authority + ", place=" + place + ", confidence=" + + confidence + "]"); + } + + @Override + protected void describeMismatchSafely(MetadataValue item, Description mismatchDescription) { + mismatchDescription.appendText("was ") + .appendValue("MetadataValue [metadataField=").appendValue(item.getMetadataField().toString('.')) + .appendValue(", value=").appendValue(item.getValue()).appendValue(", language=").appendValue(language) + .appendValue(", place=").appendValue(item.getPlace()).appendValue(", authority=") + .appendValue(item.getAuthority()).appendValue(", confidence=").appendValue(item.getConfidence() + "]"); + } + + @Override + protected boolean matchesSafely(MetadataValue metadataValue) { + return Objects.equals(metadataValue.getValue(), value) && + Objects.equals(metadataValue.getMetadataField().toString('.'), field) && + Objects.equals(metadataValue.getLanguage(), language) && + Objects.equals(metadataValue.getAuthority(), authority) && + Objects.equals(metadataValue.getPlace(), place) && + Objects.equals(metadataValue.getConfidence(), confidence); + } + + public static MetadataValueMatcher with(String field, String value, String language, + String authority, Integer place, Integer confidence) { + return new MetadataValueMatcher(field, value, language, authority, place, confidence); + } + + public static MetadataValueMatcher with(String field, String value) { + return with(field, value, null, null, 0, -1); + } + + public static MetadataValueMatcher with(String field, String value, String authority, int place, int confidence) { + return with(field, value, null, authority, place, confidence); + } + + public static MetadataValueMatcher with(String field, String value, String authority, int confidence) { + return with(field, value, null, authority, 0, confidence); + } + + public static MetadataValueMatcher with(String field, String value, int place) { + return with(field, value, null, null, place, -1); + } + +} 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 aad0e86b1e..ac32e9a2ae 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -22,6 +22,9 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import static org.dspace.content.MetadataSchemaEnum.DC; +import static org.dspace.content.authority.Choices.CF_ACCEPTED; + /** * Builder to construct Item objects * @@ -73,6 +76,11 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { public ItemBuilder withAuthor(final String authorName) { return addMetadataValue(item, MetadataSchemaEnum.DC.getName(), "contributor", "author", authorName); } + + public ItemBuilder withAuthor(final String authorName, final String authority) { + return addMetadataValue(item, DC.getName(), "contributor", "author", null, authorName, authority, 600); + } + public ItemBuilder withAuthor(final String authorName, final String authority, final int confidence) { return addMetadataValue(item, MetadataSchemaEnum.DC.getName(), "contributor", "author", null, authorName, authority, confidence); @@ -144,6 +152,13 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { return addMetadataValue(item, schema, element, qualifier, value); } + public ItemBuilder withDspaceObjectOwner(String value, String authority) { + return addMetadataValue(item, "dspace", "object", "owner", null, value, authority, CF_ACCEPTED); + } + + public ItemBuilder withDspaceObjectOwner(EPerson ePerson) { + return withDspaceObjectOwner(ePerson.getFullName(), ePerson.getID().toString()); + } public ItemBuilder makeUnDiscoverable() { item.setDiscoverable(false); return this; @@ -181,6 +196,9 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { return setAdminPermission(item, ePerson, null); } + public ItemBuilder withPersonEmail(String email) { + return addMetadataValue(item, "person", "email", null, email); + } @Override public Item build() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java new file mode 100644 index 0000000000..4eb291d8d1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.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.app.rest.converter; + +//import static org.dspace.app.orcid.model.OrcidEntityType.FUNDING; +//import static org.dspace.app.orcid.model.OrcidEntityType.PUBLICATION; + +import java.util.List; +import java.util.stream.Collectors; + +//import org.dspace.app.orcid.service.OrcidSynchronizationService; +//import org.dspace.app.profile.OrcidEntitySyncPreference; +//import org.dspace.app.profile.OrcidProfileSyncPreference; +//import org.dspace.app.profile.OrcidSynchronizationMode; +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.app.rest.model.ResearcherProfileRest; +//import org.dspace.app.rest.model.ResearcherProfileRest.OrcidSynchronizationRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This converter is responsible for transforming an model that represent a + * ResearcherProfile to the REST representation of an ResearcherProfile. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@Component +public class ResearcherProfileConverter implements DSpaceConverter { + +// @Autowired +// private OrcidSynchronizationService orcidSynchronizationService; + + @Override + public ResearcherProfileRest convert(ResearcherProfile profile, Projection projection) { + ResearcherProfileRest researcherProfileRest = new ResearcherProfileRest(); + + researcherProfileRest.setVisible(profile.isVisible()); + researcherProfileRest.setId(profile.getId()); + researcherProfileRest.setProjection(projection); + + Item item = profile.getItem(); + +// if (orcidSynchronizationService.isLinkedToOrcid(item)) { +// profile.getOrcid().ifPresent(researcherProfileRest::setOrcid); +// +// OrcidSynchronizationRest orcidSynchronization = new OrcidSynchronizationRest(); +// orcidSynchronization.setMode(getMode(item)); +// orcidSynchronization.setProfilePreferences(getProfilePreferences(item)); +// orcidSynchronization.setFundingsPreference(getFundingsPreference(item)); +// orcidSynchronization.setPublicationsPreference(getPublicationsPreference(item)); +// researcherProfileRest.setOrcidSynchronization(orcidSynchronization); +// } + + return researcherProfileRest; + } + +// private String getPublicationsPreference(Item item) { +// return orcidSynchronizationService.getEntityPreference(item, PUBLICATION) +// .map(OrcidEntitySyncPreference::name) +// .orElse(OrcidEntitySyncPreference.DISABLED.name()); +// } +// +// private String getFundingsPreference(Item item) { +// return orcidSynchronizationService.getEntityPreference(item, FUNDING) +// .map(OrcidEntitySyncPreference::name) +// .orElse(OrcidEntitySyncPreference.DISABLED.name()); +// } +// +// private List getProfilePreferences(Item item) { +// return orcidSynchronizationService.getProfilePreferences(item).stream() +// .map(OrcidProfileSyncPreference::name) +// .collect(Collectors.toList()); +// } +// +// private String getMode(Item item) { +// return orcidSynchronizationService.getSynchronizationMode(item) +// .map(OrcidSynchronizationMode::name) +// .orElse(OrcidSynchronizationMode.MANUAL.name()); +// } + + @Override + public Class getModelClass() { + return ResearcherProfile.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 70bc2c7eed..c3ebc1ca54 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 @@ -20,11 +20,16 @@ import javax.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +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.services.ConfigurationService; import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.repository.support.QueryMethodParameterConversionException; import org.springframework.http.HttpHeaders; @@ -67,6 +72,12 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH @Inject private ConfigurationService configurationService; + @Autowired + private ConverterService converterService; + + @Autowired + private Utils utils; + @ExceptionHandler({AuthorizeException.class, RESTAuthorizationException.class, AccessDeniedException.class}) protected void handleAuthorizeException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { @@ -166,6 +177,12 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH HttpStatus.BAD_REQUEST.value()); } + @ExceptionHandler(ResourceConflictException.class) + protected ResponseEntity resourceConflictException(ResourceConflictException ex) { + RestModel resource = converterService.toRest(ex.getResource(), utils.obtainProjection()); + return new ResponseEntity(resource, HttpStatus.CONFLICT); + } + @ExceptionHandler(MissingParameterException.class) protected void MissingParameterException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/PostLoggedInAction.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/PostLoggedInAction.java new file mode 100644 index 0000000000..d288ef1ecf --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/PostLoggedInAction.java @@ -0,0 +1,27 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.login; + +import org.dspace.core.Context; + +/** + * Interface for classes that need to perform some operations after the user + * login. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public interface PostLoggedInAction { + + /** + * Perform some operations after the user login. + * + * @param context the DSpace context + */ + public void loggedIn(Context context); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/LoginEventFireAction.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/LoginEventFireAction.java new file mode 100644 index 0000000000..b5df891d10 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/LoginEventFireAction.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.login.impl; + +import org.dspace.app.rest.login.PostLoggedInAction; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.services.EventService; +import org.dspace.usage.UsageEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * Implementation of {@link PostLoggedInAction} that fire an LOGIN event. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class LoginEventFireAction implements PostLoggedInAction { + + @Autowired + private EventService eventService; + + @Override + public void loggedIn(Context context) { + + HttpServletRequest request = getCurrentRequest(); + EPerson currentUser = context.getCurrentUser(); + + eventService.fireEvent(new UsageEvent(UsageEvent.Action.LOGIN, request, context, currentUser)); + + } + + private HttpServletRequest getCurrentRequest() { + return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java new file mode 100644 index 0000000000..bcc10781e9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java @@ -0,0 +1,129 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.login.impl; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.profile.service.ResearcherProfileService; +import org.dspace.app.rest.login.PostLoggedInAction; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.MetadataFieldName; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.apache.commons.collections4.IteratorUtils.toList; +import static org.dspace.content.authority.Choices.CF_ACCEPTED; + +/** + * Implementation of {@link PostLoggedInAction} that perform an automatic claim + * between the logged eperson and possible profiles without eperson present in + * the system. This pairing between eperson and profile is done starting from + * the configured metadata of the logged in user. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class ResearcherProfileAutomaticClaim implements PostLoggedInAction { + + private final static Logger LOGGER = LoggerFactory.getLogger(ResearcherProfileAutomaticClaim.class); + + @Autowired + private ResearcherProfileService researcherProfileService; + + @Autowired + private ItemService itemService; + + @Autowired + private EPersonService ePersonService; + + private final String ePersonField; + + private final String profileFiled; + + public ResearcherProfileAutomaticClaim(String ePersonField, String profileField) { + Assert.notNull(ePersonField, "An eperson field is required to perform automatic claim"); + Assert.notNull(profileField, "An profile field is required to perform automatic claim"); + this.ePersonField = ePersonField; + this.profileFiled = profileField; + } + + @Override + public void loggedIn(Context context) { + + EPerson currentUser = context.getCurrentUser(); + if (currentUser == null) { + return; + } + + try { + claimProfile(context, currentUser); + } catch (SQLException | AuthorizeException e) { + LOGGER.error("An error occurs during the profile claim by email", e); + } + + } + + private void claimProfile(Context context, EPerson currentUser) throws SQLException, AuthorizeException { + + UUID id = currentUser.getID(); + String fullName = currentUser.getFullName(); + + if (currentUserHasAlreadyResearcherProfile(context)) { + return; + } + + Item item = findClaimableItem(context, currentUser); + if (item != null) { + itemService.addMetadata(context, item, "dspace", "object", "owner", null, fullName, id.toString(), CF_ACCEPTED); + } + + } + + private boolean currentUserHasAlreadyResearcherProfile(Context context) throws SQLException, AuthorizeException { + return researcherProfileService.findById(context, context.getCurrentUser().getID()) != null; + } + + private Item findClaimableItem(Context context, EPerson currentUser) + throws SQLException, AuthorizeException { + + String value = getValueToSearchFor(context, currentUser); + if (StringUtils.isEmpty(value)) { + return null; + } + + List items = toList(itemService.findArchivedByMetadataField(context, profileFiled, value)).stream() + .filter(this::hasNotCrisOwner) + .collect(Collectors.toList()); + + return items.size() == 1 ? items.get(0) : null; + } + + private String getValueToSearchFor(Context context, EPerson currentUser) { + if ("email".equals(ePersonField)) { + return currentUser.getEmail(); + } + return ePersonService.getMetadataFirstValue(currentUser, new MetadataFieldName(ePersonField), Item.ANY); + } + + private boolean hasNotCrisOwner(Item item) { + return CollectionUtils.isEmpty(itemService.getMetadata(item, "dspace", "object", "owner", Item.ANY)); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java new file mode 100644 index 0000000000..c1d8f69254 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java @@ -0,0 +1,134 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import org.dspace.app.rest.RestResourceController; + +import java.util.List; +import java.util.UUID; + +/** + * The Researcher Profile REST resource. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@LinksRest(links = { + @LinkRest(name = ResearcherProfileRest.ITEM, method = "getItem"), + @LinkRest(name = ResearcherProfileRest.EPERSON, method = "getEPerson") +}) +public class ResearcherProfileRest extends BaseObjectRest { + + private static final long serialVersionUID = 1L; + // changed from RestModel.CRIS to RestModel.EPERSON + public static final String CATEGORY = RestModel.EPERSON; + public static final String NAME = "profile"; + + public static final String ITEM = "item"; + public static final String EPERSON = "eperson"; + + private boolean visible; + +// @JsonInclude(Include.NON_NULL) +// private String orcid; + +// @JsonInclude(Include.NON_NULL) +// private OrcidSynchronizationRest orcidSynchronization; + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + +// public OrcidSynchronizationRest getOrcidSynchronization() { +// return orcidSynchronization; +// } + +// public void setOrcidSynchronization(OrcidSynchronizationRest orcidSynchronization) { +// this.orcidSynchronization = orcidSynchronization; +// } + +// public String getOrcid() { +// return orcid; +// } +// +// public void setOrcid(String orcid) { +// this.orcid = orcid; +// } + + @Override + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + /** + * Inner class to model ORCID synchronization preferences and mode. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +// public static class OrcidSynchronizationRest { +// +// private String mode; +// +// private String publicationsPreference; +// +// private String fundingsPreference; +// +// private List profilePreferences; +// +// public String getMode() { +// return mode; +// } +// +// public void setMode(String mode) { +// this.mode = mode; +// } +// +// public List getProfilePreferences() { +// return profilePreferences; +// } +// +// public void setProfilePreferences(List profilePreferences) { +// this.profilePreferences = profilePreferences; +// } +// +// public String getPublicationsPreference() { +// return publicationsPreference; +// } +// +// public void setPublicationsPreference(String publicationsPreference) { +// this.publicationsPreference = publicationsPreference; +// } +// +// public String getFundingsPreference() { +// return fundingsPreference; +// } +// +// public void setFundingsPreference(String fundingsPreference) { +// this.fundingsPreference = fundingsPreference; +// } +// +// } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ResearcherProfileResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ResearcherProfileResource.java new file mode 100644 index 0000000000..3b034c1506 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ResearcherProfileResource.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.ResearcherProfileRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * This class serves as a wrapper class to wrap the SearchConfigurationRest into + * a HAL resource. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@RelNameDSpaceResource(ResearcherProfileRest.NAME) +public class ResearcherProfileResource extends DSpaceResource { + + public ResearcherProfileResource(ResearcherProfileRest data, Utils utils) { + super(data, utils); + } + + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.java new file mode 100644 index 0000000000..ed7b286003 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileEPersonLinkRepository.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.repository; + +import java.sql.SQLException; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.app.profile.service.ResearcherProfileService; +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.ResearcherProfileRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +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 "ePerson" subresource of an individual researcher + * profile. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME + "." + ResearcherProfileRest.EPERSON) +public class ResearcherProfileEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private EPersonService ePersonService; + + @Autowired + private ResearcherProfileService researcherProfileService; + + /** + * Returns the ePerson related to the Research profile with the given UUID. + * + * @param request the http servlet request + * @param id the profile UUID + * @param pageable the optional pageable + * @param projection the projection object + * @return the ePerson rest representation + */ + @PreAuthorize("hasPermission(#id, 'PROFILE', 'READ')") + public EPersonRest getEPerson(@Nullable HttpServletRequest request, UUID id, + @Nullable Pageable pageable, Projection projection) { + + try { + Context context = obtainContext(); + + ResearcherProfile profile = researcherProfileService.findById(context, id); + if (profile == null) { + throw new ResourceNotFoundException("No such profile with UUID: " + id); + } + + EPerson ePerson = ePersonService.find(context, id); + if (ePerson == null) { + throw new ResourceNotFoundException("No such eperson related to a profile with EPerson UUID: " + id); + } + + return converter.toRest(ePerson, projection); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.java new file mode 100644 index 0000000000..827d554149 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileItemLinkRepository.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.repository; + +import java.sql.SQLException; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.app.profile.service.ResearcherProfileService; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.ResearcherProfileRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "item" subresource of an individual researcher profile. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME + "." + ResearcherProfileRest.ITEM) +public class ResearcherProfileItemLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private ResearcherProfileService researcherProfileService; + + /** + * Returns the item related to the Research profile with the given UUID. + * + * @param request the http servlet request + * @param id the profile UUID + * @param pageable the optional pageable + * @param projection the projection object + * @return the item rest representation + */ + @PreAuthorize("hasPermission(#id, 'PROFILE', 'READ')") + public ItemRest getItem(@Nullable HttpServletRequest request, UUID id, + @Nullable Pageable pageable, Projection projection) { + + try { + Context context = obtainContext(); + + ResearcherProfile profile = researcherProfileService.findById(context, id); + if (profile == null) { + throw new ResourceNotFoundException("No such item related to a profile with EPerson UUID: " + id); + } + + return converter.toRest(profile.getItem(), projection); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java new file mode 100644 index 0000000000..8be3720f52 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java @@ -0,0 +1,189 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import org.apache.commons.collections.CollectionUtils; +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.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.ResearcherProfileRest; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.ResourcePatch; +import org.dspace.app.rest.security.DSpacePermissionEvaluator; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.dspace.util.UUIDUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Conditional; +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.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.net.URI; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +/** + * This is the repository responsible of exposing researcher profiles. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME) +@ConditionalOnProperty( + value="researcher-profile.type" +) +public class ResearcherProfileRestRepository extends DSpaceRestRepository { + + public static final String NO_VISIBILITY_CHANGE_MSG = "Refused to perform the Researcher Profile patch based " + + "on a token without changing the visibility"; + + @Autowired + private ResearcherProfileService researcherProfileService; + + @Autowired + private DSpacePermissionEvaluator permissionEvaluator; + + @Autowired + private EPersonService ePersonService; + + @Autowired + private ResourcePatch resourcePatch; + + @Override + @PreAuthorize("hasPermission(#id, 'PROFILE', 'READ')") + public ResearcherProfileRest findOne(Context context, UUID id) { + try { + ResearcherProfile profile = researcherProfileService.findById(context, id); + if (profile == null) { + return null; + } + return converter.toRest(profile, utils.obtainProjection()); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("isAuthenticated()") + protected ResearcherProfileRest createAndReturn(Context context) throws AuthorizeException, SQLException { + + UUID id = getEPersonIdFromRequest(context); + if (isNotAuthorized(id, "WRITE")) { + throw new AuthorizeException("User unauthorized to create a new profile for user " + id); + } + + EPerson ePerson = ePersonService.find(context, id); + if (ePerson == null) { + throw new UnprocessableEntityException("No EPerson exists with id: " + id); + } + + try { + ResearcherProfile newProfile = researcherProfileService.createAndReturn(context, ePerson); + return converter.toRest(newProfile, utils.obtainProjection()); + } catch (SearchServiceException e) { + throw new RuntimeException(e.getMessage(), e); + } + + } + + @Override + protected ResearcherProfileRest createAndReturn(final Context context, final List list) + throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { + if (CollectionUtils.isEmpty(list) || list.size() > 1) { + throw new IllegalArgumentException("Uri list must contain exactly one element"); + } + + + UUID id = getEPersonIdFromRequest(context); + if (isNotAuthorized(id, "WRITE")) { + throw new AuthorizeException("User unauthorized to create a new profile for user " + id); + } + + EPerson ePerson = ePersonService.find(context, id); + if (ePerson == null) { + throw new UnprocessableEntityException("No EPerson exists with id: " + id); + } + + try { + ResearcherProfile newProfile = researcherProfileService + .claim(context, ePerson, URI.create(list.get(0))); + return converter.toRest(newProfile, utils.obtainProjection()); + } catch (SearchServiceException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); + } + + @Override + @PreAuthorize("hasPermission(#id, 'PROFILE', 'WRITE')") + protected void delete(Context context, UUID id) { + try { + researcherProfileService.deleteById(context, id); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("hasPermission(#id, 'PROFILE', #patch)") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, + UUID id, Patch patch) throws SQLException, AuthorizeException { + + ResearcherProfile profile = researcherProfileService.findById(context, id); + if (profile == null) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); + } + + resourcePatch.patch(context, profile, patch.getOperations()); + + } + + @Override + public Class getDomainClass() { + return ResearcherProfileRest.class; + } + + + private UUID getEPersonIdFromRequest(Context context) { + HttpServletRequest request = getRequestService().getCurrentRequest().getHttpServletRequest(); + + String ePersonId = request.getParameter("eperson"); + if (ePersonId == null) { + return context.getCurrentUser().getID(); + } + + UUID uuid = UUIDUtils.fromString(ePersonId); + if (uuid == null) { + throw new DSpaceBadRequestException("The provided eperson parameter is not a valid uuid"); + } + return uuid; + } + + private boolean isNotAuthorized(UUID id, String permission) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return !permissionEvaluator.hasPermission(authentication, id, "PROFILE", permission); + } + +} 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 new file mode 100644 index 0000000000..a9cd66b4d2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.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.repository.patch.operation; + +import java.sql.SQLException; + +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.app.profile.service.ResearcherProfileService; +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.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Implementation for ResearcherProfile visibility patches. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/cris/profiles/<:id-eperson> -H " + * Content-Type: application/json" -d '[{ "op": "replace", "path": " + * /visible", "value": true]' + * + */ +@Component +public class ResearcherProfileVisibleReplaceOperation extends PatchOperation { + + @Autowired + private ResearcherProfileService researcherProfileService; + + /** + * Path in json body of patch that uses this operation. + */ + public static final String OPERATION_VISIBLE_CHANGE = "/visible"; + + @Override + public ResearcherProfile perform(Context context, ResearcherProfile profile, Operation operation) + throws SQLException { + + Object value = operation.getValue(); + if (value == null | !(value instanceof Boolean)) { + throw new UnprocessableEntityException("The /visible value must be a boolean (true|false)"); + } + + try { + researcherProfileService.changeVisibility(context, profile, (boolean) value); + } catch (AuthorizeException e) { + throw new RESTAuthorizationException("Unauthorized user for profile visibility change"); + } + + return profile; + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof ResearcherProfile + && operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE) + && operation.getPath().trim().equalsIgnoreCase(OPERATION_VISIBLE_CHANGE)); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java index 20844ec946..aabf19666a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java @@ -11,12 +11,15 @@ import static org.dspace.app.rest.security.WebSecurityConfiguration.ADMIN_GRANT; import static org.dspace.app.rest.security.WebSecurityConfiguration.AUTHENTICATED_GRANT; import java.sql.SQLException; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.login.PostLoggedInAction; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.util.AuthorizeUtil; import org.dspace.authenticate.AuthenticationMethod; @@ -62,6 +65,16 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider @Autowired private HttpServletRequest request; + @Autowired(required = false) + private List postLoggedInActions; + + @PostConstruct + public void postConstruct() { + if (postLoggedInActions == null) { + postLoggedInActions = Collections.emptyList(); + } + } + @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { Context context = ContextUtil.obtainContext(request); @@ -122,6 +135,11 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider .getHeader(newContext, "login", "type=explicit")); output = createAuthentication(newContext); + + for (PostLoggedInAction action : postLoggedInActions) { + action.loggedIn(newContext); + } + } else { log.info(LogHelper.getHeader(newContext, "failed_login", "email=" + name + ", result=" diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..7545e9ad9c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.ResearcherProfileRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.dspace.util.UUIDUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.io.Serializable; +import java.util.UUID; + +import static org.dspace.app.rest.security.DSpaceRestPermission.*; + +/** + * + * An authenticated user is allowed to view, update or delete his or her own + * data. This {@link RestPermissionEvaluatorPlugin} implements that requirement. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@Component +public class ResearcherProfileRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + @Autowired + private RequestService requestService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + + if (!READ.equals(restPermission) && !WRITE.equals(restPermission) && !DELETE.equals(restPermission)) { + return false; + } + + if (!StringUtils.equalsIgnoreCase(targetType, ResearcherProfileRest.NAME)) { + return false; + } + + UUID id = UUIDUtils.fromString(targetId.toString()); + if (id == null) { + return false; + } + + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext((HttpServletRequest) request.getServletRequest()); + + EPerson currentUser = context.getCurrentUser(); + if (currentUser == null) { + return false; + } + + if (id.equals(currentUser.getID())) { + return true; + } + + return false; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java new file mode 100644 index 0000000000..f5a460b298 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java @@ -0,0 +1,1032 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 com.jayway.jsonpath.JsonPath; +import org.dspace.app.rest.model.MetadataValueRest; +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.authorize.AuthorizeException; +import org.dspace.builder.*; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; +import org.dspace.util.UUIDUtils; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; + +import static com.jayway.jsonpath.JsonPath.read; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static java.util.Arrays.asList; +import static java.util.UUID.fromString; +import static org.dspace.app.matcher.LambdaMatcher.has; +import static org.dspace.app.matcher.MetadataValueMatcher.with; +import static org.dspace.app.rest.matcher.HalMatcher.matchLinks; +import static org.dspace.app.rest.matcher.MetadataMatcher.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Integration tests for {@link ResearcherProfileRestRepository}. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private ItemService itemService; + + @Autowired + private GroupService groupService; + + private EPerson user; + + private EPerson anotherUser; + + private Collection personCollection; + + private Group administrators; + + /** + * Tests setup. + */ + @Override + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + + user = EPersonBuilder.createEPerson(context) + .withEmail("user@example.com") + .withPassword(password) + .build(); + + anotherUser = EPersonBuilder.createEPerson(context) + .withEmail("anotherUser@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + personCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Profile Collection") + .withEntityType("Person") + .withSubmitterGroup(user) + .withTemplateItem() + .build(); + + administrators = groupService.findByName(context, Group.ADMIN); + +// itemService.addMetadata(context, personCollection.getTemplateItem(), "cris", "policy", +// "group", null, administrators.getName()); + + configurationService.setProperty("researcher-profile.collection.uuid", personCollection.getID().toString()); + configurationService.setProperty("claimable.entityType", "Person"); + + context.setCurrentUser(user); + + context.restoreAuthSystemState(); + + } + + /** + * Verify that the findById endpoint returns the own profile. + * + * @throws Exception + */ + @Test + public void testFindById() throws Exception { + + UUID id = user.getID(); + String name = user.getFullName(); + + String authToken = getAuthToken(user.getEmail(), password); + + context.turnOffAuthorisationSystem(); + + ItemBuilder.createItem(context, personCollection) + .withDspaceObjectOwner(name, id.toString()) + .build(); + + context.restoreAuthSystemState(); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(true))) + .andExpect(jsonPath("$.type", is("profile"))) + .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson"))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("item"))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id.toString(), 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("eperson"))) + .andExpect(jsonPath("$.name", is(name))); + + } + + /** + * Verify that the an admin user can call the findById endpoint to get a + * profile. + * + * @throws Exception + */ + @Test + public void testFindByIdWithAdmin() throws Exception { + + UUID id = user.getID(); + String name = user.getFullName(); + + String authToken = getAuthToken(admin.getEmail(), password); + + context.turnOffAuthorisationSystem(); + + ItemBuilder.createItem(context, personCollection) + .withDspaceObjectOwner(name, id.toString()) + .build(); + + context.restoreAuthSystemState(); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(true))) + .andExpect(jsonPath("$.type", is("profile"))) + .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson"))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("item"))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id.toString(), 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("eperson"))) + .andExpect(jsonPath("$.name", is(name))); + + } + + /** + * Verify that a standard user can't access the profile of another user. + * + * @throws Exception + */ + @Test + public void testFindByIdWithoutOwnerUser() throws Exception { + + UUID id = user.getID(); + String name = user.getFullName(); + + String authToken = getAuthToken(anotherUser.getEmail(), password); + + context.turnOffAuthorisationSystem(); + + ItemBuilder.createItem(context, personCollection) + .withDspaceObjectOwner(name, id.toString()) + .build(); + + context.restoreAuthSystemState(); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isForbidden()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isForbidden()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) + .andExpect(status().isForbidden()); + + } + + /** + * Verify that the createAndReturn endpoint create a new researcher profile. + * + * @throws Exception + */ + @Test + public void testCreateAndReturn() throws Exception { + + String id = user.getID().toString(); + String name = user.getName(); + + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(false))) + .andExpect(jsonPath("$.type", is("profile"))) + .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson"))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("item"))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id.toString(), 0))) + //.andExpect(jsonPath("$.metadata", matchMetadata("cris.policy.group", administrators.getName(), +// UUIDUtils.toString(administrators.getID()), 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("eperson"))) + .andExpect(jsonPath("$.name", is(name))); + } + + /** + * Verify that an admin can call the createAndReturn endpoint to store a new + * researcher profile related to another user. + * + * @throws Exception + */ + @Test + public void testCreateAndReturnWithAdmin() throws Exception { + + String id = user.getID().toString(); + String name = user.getName(); + + configurationService.setProperty("researcher-profile.collection.uuid", null); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .param("eperson", id) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(false))) + .andExpect(jsonPath("$.type", is("profile"))) + .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson"))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("item"))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id.toString(), 0))) +// .andExpect(jsonPath("$.metadata", matchMetadata("cris.policy.group", administrators.getName(), +// UUIDUtils.toString(administrators.getID()), 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("eperson"))) + .andExpect(jsonPath("$.name", is(name))); + + authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(false))) + .andExpect(jsonPath("$.type", is("profile"))) + .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson"))); + } + + /** + * Verify that a standard user can't call the createAndReturn endpoint to store + * a new researcher profile related to another user. + * + * @throws Exception + */ + @Test + public void testCreateAndReturnWithoutOwnUser() throws Exception { + + String authToken = getAuthToken(anotherUser.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .param("eperson", user.getID().toString()) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isForbidden()); + + } + + /** + * Verify that a conflict occurs if an user that have already a profile call the + * createAndReturn endpoint. + * + * @throws Exception + */ + @Test + public void testCreateAndReturnWithProfileAlreadyAssociated() throws Exception { + + String id = user.getID().toString(); + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(false))) + .andExpect(jsonPath("$.type", is("profile"))); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(false))) + .andExpect(jsonPath("$.type", is("profile"))); + + } + + /** + * Verify that an unprocessable entity status is back when the createAndReturn + * is called to create a profile for an unknown user. + * + * @throws Exception + */ + @Test + public void testCreateAndReturnWithUnknownEPerson() throws Exception { + + String unknownId = UUID.randomUUID().toString(); + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .param("eperson", unknownId) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + /** + * Verify that a user can delete his profile using the delete endpoint. + * + * @throws Exception + */ + @Test + public void testDelete() throws Exception { + + configurationService.setProperty("researcher-profile.hard-delete.enabled", false); + + String id = user.getID().toString(); + String authToken = getAuthToken(user.getEmail(), password); + AtomicReference itemIdRef = new AtomicReference(); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata", matchMetadataNotEmpty("dspace.object.owner")))) + .andDo(result -> itemIdRef.set(fromString(read(result.getResponse().getContentAsString(), "$.id")))); + + getClient(authToken).perform(delete("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNoContent()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNotFound()); + + getClient(authToken).perform(get("/api/core/items/{id}", itemIdRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata", matchMetadataDoesNotExist("dspace.object.owner")))); + + } + + /** + * Verify that a user can hard delete his profile using the delete endpoint. + * + * @throws Exception + */ + @Test + public void testHardDelete() throws Exception { + + configurationService.setProperty("researcher-profile.hard-delete.enabled", true); + + String id = user.getID().toString(); + String authToken = getAuthToken(user.getEmail(), password); + AtomicReference itemIdRef = new AtomicReference(); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasJsonPath("$.metadata", matchMetadataNotEmpty("dspace.object.owner")))) + .andDo(result -> itemIdRef.set(fromString(read(result.getResponse().getContentAsString(), "$.id")))); + + getClient(authToken).perform(delete("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNoContent()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNotFound()); + + getClient(authToken).perform(get("/api/core/items/{id}", itemIdRef.get())) + .andExpect(status().isNotFound()); + + } + + /** + * Verify that an admin can delete a profile of another user using the delete + * endpoint. + * + * @throws Exception + */ + @Test + public void testDeleteWithAdmin() throws Exception { + + String id = user.getID().toString(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String userToken = getAuthToken(user.getEmail(), password); + + getClient(userToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNotFound()); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNotFound()); + } + + /** + * Verify that an user can delete his profile using the delete endpoint even if + * was created by an admin. + * + * @throws Exception + */ + @Test + public void testDeleteProfileCreatedByAnAdmin() throws Exception { + + String id = user.getID().toString(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String userToken = getAuthToken(user.getEmail(), password); + + getClient(adminToken).perform(post("/api/eperson/profiles/") + .param("eperson", id) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + getClient(adminToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + getClient(userToken).perform(delete("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNoContent()); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNotFound()); + + getClient(adminToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNotFound()); + + } + + /** + * Verify that a standard user can't call the delete endpoint to delete a + * researcher profile related to another user. + * + * @throws Exception + */ + @Test + public void testDeleteWithoutOwnUser() throws Exception { + + String id = user.getID().toString(); + + String userToken = getAuthToken(user.getEmail(), password); + String anotherUserToken = getAuthToken(anotherUser.getEmail(), password); + + getClient(userToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + getClient(anotherUserToken).perform(delete("/api/eperson/profiles/{id}", id)) + .andExpect(status().isForbidden()); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + } + + /** + * Verify that an user can change the profile visibility using the patch endpoint. + * + * @throws Exception + */ + @Test + public void testPatchToChangeVisibleAttribute() throws Exception { + + String id = user.getID().toString(); + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.visible", is(false))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(false))); + + // change the visibility to true + List operations = asList(new ReplaceOperation("/visible", true)); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", id) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(true))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(true))); + + // change the visibility to false + operations = asList(new ReplaceOperation("/visible", false)); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", id) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(false))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(false))); + + } + + /** + * Verify that an user can not change the profile visibility of another user + * using the patch endpoint. + * + * @throws Exception + */ + @Test + public void testPatchToChangeVisibleAttributeWithoutOwnUser() throws Exception { + + String id = user.getID().toString(); + + String userToken = getAuthToken(user.getEmail(), password); + String anotherUserToken = getAuthToken(anotherUser.getEmail(), password); + + getClient(userToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.visible", is(false))); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + // try to change the visibility to true + List operations = asList(new ReplaceOperation("/visible", true)); + + getClient(anotherUserToken).perform(patch("/api/eperson/profiles/{id}", id) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isForbidden()); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(false))); + } + + /** + * Verify that an admin can change the profile visibility of another user using + * the patch endpoint. + * + * @throws Exception + */ + @Test + public void testPatchToChangeVisibleAttributeWithAdmin() throws Exception { + + String id = user.getID().toString(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String userToken = getAuthToken(user.getEmail(), password); + + getClient(userToken).perform(post("/api/eperson/profiles/") + .param("eperson", id) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + // change the visibility to true + List operations = asList(new ReplaceOperation("/visible", true)); + + getClient(adminToken).perform(patch("/api/eperson/profiles/{id}", id) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(true))); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(true))); + } + + /** + * Verify that an user can change the visibility of his profile using the patch + * endpoint even if was created by an admin. + * + * @throws Exception + */ + @Test + public void testPatchToChangeVisibilityOfProfileCreatedByAnAdmin() throws Exception { + + String id = user.getID().toString(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String userToken = getAuthToken(user.getEmail(), password); + + getClient(adminToken).perform(post("/api/eperson/profiles/") + .param("eperson", id) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + getClient(adminToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + // change the visibility to true + List operations = asList(new ReplaceOperation("/visible", true)); + + getClient(userToken).perform(patch("/api/eperson/profiles/{id}", id) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(true))); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.visible", is(true))); + } + + /** + * Verify that after an user login an automatic claim between the logged eperson + * and possible profiles without eperson is done. + * + * @throws Exception + */ + @Test + public void testAutomaticProfileClaimByEmail() throws Exception { + + String id = user.getID().toString(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // create and delete a profile + getClient(adminToken).perform(post("/api/eperson/profiles/") + .param("eperson", id) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + String firstItemId = getItemIdByProfileId(adminToken, id); + + MetadataValueRest valueToAdd = new MetadataValueRest(user.getEmail()); + List operations = asList(new AddOperation("/metadata/person.email", valueToAdd)); + + getClient(adminToken).perform(patch(BASE_REST_SERVER_URL + "/api/core/items/{id}", firstItemId) + .contentType(MediaType.APPLICATION_JSON) + .content(getPatchContent(operations))) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNotFound()); + + // the automatic claim is done after the user login + String userToken = getAuthToken(user.getEmail(), password); + + getClient(userToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + // the profile item should be the same + String secondItemId = getItemIdByProfileId(adminToken, id); + assertEquals("The item should be the same", firstItemId, secondItemId); + + } + + @Test + public void testNoAutomaticProfileClaimOccursIfManyClaimableItemsAreFound() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withNameInMetadata("Test", "User") + .withPassword(password) + .withEmail("test@email.it") + .build(); + + ItemBuilder.createItem(context, personCollection) + .withTitle("Test User") + .build(); + + ItemBuilder.createItem(context, personCollection) + .withTitle("Test User 2") + .build(); + + context.restoreAuthSystemState(); + + String epersonId = ePerson.getID().toString(); + + getClient(getAuthToken(ePerson.getEmail(), password)) + .perform(get("/api/eperson/profiles/{id}", epersonId)) + .andExpect(status().isNotFound()); + + } + + @Test + public void testNoAutomaticProfileClaimOccursIfTheUserHasAlreadyAProfile() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withNameInMetadata("Test", "User") + .withPassword(password) + .withEmail("test@email.it") + .build(); + + context.restoreAuthSystemState(); + + String epersonId = ePerson.getID().toString(); + + String token = getAuthToken(ePerson.getEmail(), password); + + getClient(token).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + getClient(token).perform(get("/api/eperson/profiles/{id}", epersonId)) + .andExpect(status().isOk()); + + String profileItemId = getItemIdByProfileId(token, epersonId); + + context.turnOffAuthorisationSystem(); + + ItemBuilder.createItem(context, personCollection) + .withTitle("Test User") + .build(); + + context.restoreAuthSystemState(); + + token = getAuthToken(ePerson.getEmail(), password); + + String newProfileItemId = getItemIdByProfileId(token, epersonId); + assertEquals("The item should be the same", newProfileItemId, profileItemId); + + } + + @Test + public void testNoAutomaticProfileClaimOccursIfTheFoundProfileIsAlreadyClaimed() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withNameInMetadata("Test", "User") + .withPassword(password) + .withEmail("test@email.it") + .build(); + + ItemBuilder.createItem(context, personCollection) + .withTitle("Admin User") + .withPersonEmail("test@email.it") + .withDspaceObjectOwner("Admin User", admin.getID().toString()) + .build(); + + context.restoreAuthSystemState(); + + String epersonId = ePerson.getID().toString(); + + String token = getAuthToken(ePerson.getEmail(), password); + + getClient(token).perform(get("/api/eperson/profiles/{id}", epersonId)) + .andExpect(status().isNotFound()); + + } + + @Test + public void researcherProfileClaim() throws Exception { + String id = user.getID().toString(); + String name = user.getName(); + + context.turnOffAuthorisationSystem(); + + final Item person = ItemBuilder.createItem(context, personCollection) + .withTitle("dc.title") + .build(); + + final Item otherPerson = ItemBuilder.createItem(context, personCollection) + .withTitle("dc.title") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/items/" + person.getID().toString())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(id))) + .andExpect(jsonPath("$.type", is("profile"))) + .andExpect(jsonPath("$", + matchLinks("http://localhost/api/eperson/profiles/" + user.getID(), "item", "eperson"))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) + .andExpect(status().isOk()); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("item"))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id, 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("eperson"))) + .andExpect(jsonPath("$.name", is(name))); + + // trying to claim another profile + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/items/" + otherPerson.getID().toString())) + .andExpect(status().isConflict()); + + // other person trying to claim same profile + context.turnOffAuthorisationSystem(); + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("foo@bar.baz") + .withPassword(password) + .withNameInMetadata("Test", "User") + .build(); + + context.restoreAuthSystemState(); + + final String ePersonToken = getAuthToken(ePerson.getEmail(), password); + + getClient(ePersonToken).perform(post("/api/eperson/profiles/") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/items/" + person.getID().toString())) + .andExpect(status().isBadRequest()); + + getClient(authToken).perform(delete("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNoContent()); + } + + @Test + public void claimForNotAllowedEntityType() throws Exception { + String id = user.getID().toString(); + String name = user.getName(); + + context.turnOffAuthorisationSystem(); + + final Collection publications = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .build(); + + final Item publication = ItemBuilder.createItem(context, publications) + .withTitle("title") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/items/" + publication.getID().toString())) + .andExpect(status().isBadRequest()); + } + + @Test + public void testCloneFromExternalSourceRecordNotFound() throws Exception { + + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken) + .perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/integration/externalsources/orcid/entryValues/FAKE")) + .andExpect(status().isBadRequest()); + } + + @Test + public void testCloneFromExternalSourceMultipleUri() throws Exception { + + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken) + .perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/integration/externalsources/orcid/entryValues/id \n " + + "http://localhost:8080/server/api/integration/externalsources/dspace/entryValues/id")) + .andExpect(status().isBadRequest()); + + } + + @Test + public void testCloneFromExternalProfileAlreadyAssociated() throws Exception { + + String id = user.getID().toString(); + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/").contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()).andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(false))).andExpect(jsonPath("$.type", is("profile"))); + + getClient(authToken) + .perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/integration/externalsources/orcid/entryValues/id")) + .andExpect(status().isConflict()); + } + + @Test + public void testCloneFromExternalCollectionNotSet() throws Exception { + + configurationService.setProperty("researcher-profile.collection.uuid", "not-existing"); + String id = user.getID().toString(); + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/").contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()).andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.visible", is(false))).andExpect(jsonPath("$.type", is("profile"))); + + getClient(authToken) + .perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/integration/externalsources/orcid/entryValues/id \n " + + "http://localhost:8080/server/api/integration/externalsources/dspace/entryValues/id")) + .andExpect(status().isBadRequest()); + } + + private String getItemIdByProfileId(String token, String id) throws SQLException, Exception { + MvcResult result = getClient(token).perform(get("/api/eperson/profiles/{id}/item", id)) + .andExpect(status().isOk()) + .andReturn(); + + return readAttributeFromResponse(result, "$.id"); + } + + private T readAttributeFromResponse(MvcResult result, String attribute) throws UnsupportedEncodingException { + return JsonPath.read(result.getResponse().getContentAsString(), attribute); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java index 0b6c8c972a..4a6ca96dfe 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java @@ -15,6 +15,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import org.hamcrest.Matcher; +import org.hamcrest.Matchers; import org.hamcrest.core.StringEndsWith; /** @@ -67,6 +68,22 @@ public class MetadataMatcher { return hasJsonPath("$.['" + key + "'][" + position + "].value", is(value)); } + /** + * Gets a matcher to ensure a given value is present at a specific position in + * the list of values for a given key. + * + * @param key the metadata key. + * @param value the value that must be present. + * @param authority the authority that must be present. + * @param position the position it must be present at. + * @return the matcher. + */ + public static Matcher matchMetadata(String key, String value, String authority, int position) { + Matcher hasValue = hasJsonPath("$.['" + key + "'][" + position + "].value", is(value)); + Matcher hasAuthority = hasJsonPath("$.['" + key + "'][" + position + "].authority", is(authority)); + return Matchers.allOf(hasValue, hasAuthority); + } + /** * Gets a matcher to ensure a given key is not present. * diff --git a/dspace/config/modules/authority.cfg b/dspace/config/modules/authority.cfg new file mode 100644 index 0000000000..664730a8bf --- /dev/null +++ b/dspace/config/modules/authority.cfg @@ -0,0 +1,15 @@ +#---------------------------------------------------------------# +#----------------- AUTHORITY CONFIGURATIONS --------------------# +#---------------------------------------------------------------# +# These configs are used by the authority framework # +#---------------------------------------------------------------# + +##### Authority Control Settings ##### + +plugin.named.org.dspace.content.authority.ChoiceAuthority = \ +org.dspace.content.authority.EPersonAuthority = EPersonAuthority + + +choices.plugin.dspace.object.owner = EPersonAuthority +choices.presentation.dspace.object.owner = suggest +authority.controlled.dspace.object.owner = true \ No newline at end of file diff --git a/dspace/config/registries/dspace-types.xml b/dspace/config/registries/dspace-types.xml index ab0c1bc40f..a252fbced3 100644 --- a/dspace/config/registries/dspace-types.xml +++ b/dspace/config/registries/dspace-types.xml @@ -43,4 +43,12 @@ enabled Stores a boolean text value (true or false) to indicate if the iiif feature is enabled or not for the dspace object. If absent the value is derived from the parent dspace object + + + dspace + object + owner + Stores the reference to the eperson that own the item + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 591a4ef3f4..5d5157273b 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -63,6 +63,8 @@ + + + + + + + + + + + + \ No newline at end of file From c25bdd12222e3957d2e4a1c798c21018656dcc58 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 29 Mar 2022 17:14:15 +0200 Subject: [PATCH 0781/1254] [CST-5306] Migrate Researcher Profile (REST) . --- dspace/config/dspace.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index d5c28da096..269955fe8b 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1628,3 +1628,4 @@ include = ${module_dir}/translator.cfg include = ${module_dir}/usage-statistics.cfg include = ${module_dir}/versioning.cfg include = ${module_dir}/workflow.cfg +include = ${module_dir}/authority.cfg \ No newline at end of file From 3210d9ce38176c6007eeab05f5a9d369548a3647 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 30 Mar 2022 13:19:07 +0200 Subject: [PATCH 0782/1254] [CST-5288] Add CorsMapping for actuator endpoint --- .../java/org/dspace/app/rest/Application.java | 44 +++++++++++-------- .../configuration/ActuatorConfiguration.java | 4 ++ 2 files changed, 29 insertions(+), 19 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 e0ea0cccb0..29d6853f4e 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.sql.SQLException; import java.util.List; import javax.servlet.Filter; +import org.dspace.app.rest.configuration.ActuatorConfiguration; import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.model.hateoas.DSpaceLinkRelationProvider; import org.dspace.app.rest.parameter.resolver.SearchFilterResolver; @@ -65,6 +66,9 @@ public class Application extends SpringBootServletInitializer { @Autowired private ApplicationConfig configuration; + @Autowired + private ActuatorConfiguration actuatorConfiguration; + @Scheduled(cron = "${sitemap.cron:-}") public void generateSitemap() throws IOException, SQLException { GenerateSitemaps.generateSitemapsScheduled(); @@ -167,29 +171,31 @@ public class Application extends SpringBootServletInitializer { boolean corsAllowCredentials = configuration.getCorsAllowCredentials(); boolean iiifAllowCredentials = configuration.getIiifAllowCredentials(); + if (corsAllowedOrigins != null) { - registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) - // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid - // for our Access-Control-Allow-Origin header - // for our Access-Control-Allow-Origin header - .allowCredentials(corsAllowCredentials).allowedOrigins(corsAllowedOrigins) - // 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") - // Allow list of response headers allowed to be sent by us (the server) to the client - .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + addCorsMapping(registry, "/api/**", corsAllowedOrigins, corsAllowCredentials); + addCorsMapping(registry, actuatorConfiguration.getActuatorBasePath() + "/**", + corsAllowedOrigins, corsAllowCredentials); } + if (iiifAllowedOrigins != null) { - registry.addMapping("/iiif/**").allowedMethods(CorsConfiguration.ALL) - // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid - // for our Access-Control-Allow-Origin header - .allowCredentials(iiifAllowCredentials).allowedOrigins(iiifAllowedOrigins) - // 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") - // Allow list of response headers allowed to be sent by us (the server) to the client - .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + addCorsMapping(registry, "/iiif/**", iiifAllowedOrigins, iiifAllowCredentials); } + + } + + private void addCorsMapping(CorsRegistry registry, String pathPattern, + String[] allowedOrigins, boolean allowCredentials) { + + registry.addMapping(pathPattern).allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + .allowCredentials(allowCredentials).allowedOrigins(allowedOrigins) + // 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") + // Allow list of response headers allowed to be sent by us (the server) to the client + .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java index f2054a7101..42a7bfab15 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/configuration/ActuatorConfiguration.java @@ -88,4 +88,8 @@ public class ActuatorConfiguration { return new GeoIpHealthIndicator(); } + public String getActuatorBasePath() { + return actuatorBasePath; + } + } From 95ba7d805bb2f97e97956e51f81d6f13677e134f Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 30 Mar 2022 16:29:52 +0200 Subject: [PATCH 0783/1254] [CST-5288] Fixed CORS configuration for actuator endpoints --- .../java/org/dspace/app/rest/Application.java | 45 +++++++++---------- dspace/config/dspace.cfg | 6 +++ 2 files changed, 26 insertions(+), 25 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 29d6853f4e..71f15d39de 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,7 +12,6 @@ import java.sql.SQLException; import java.util.List; import javax.servlet.Filter; -import org.dspace.app.rest.configuration.ActuatorConfiguration; import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.model.hateoas.DSpaceLinkRelationProvider; import org.dspace.app.rest.parameter.resolver.SearchFilterResolver; @@ -66,9 +65,6 @@ public class Application extends SpringBootServletInitializer { @Autowired private ApplicationConfig configuration; - @Autowired - private ActuatorConfiguration actuatorConfiguration; - @Scheduled(cron = "${sitemap.cron:-}") public void generateSitemap() throws IOException, SQLException { GenerateSitemaps.generateSitemapsScheduled(); @@ -164,6 +160,7 @@ public class Application extends SpringBootServletInitializer { @Override public void addCorsMappings(@NonNull CorsRegistry registry) { // Get allowed origins for api and iiif endpoints. + // The actuator endpoints are configured using management.endpoints.web.cors.* properties String[] corsAllowedOrigins = configuration .getCorsAllowedOrigins(configuration.getCorsAllowedOriginsConfig()); String[] iiifAllowedOrigins = configuration @@ -171,31 +168,29 @@ public class Application extends SpringBootServletInitializer { boolean corsAllowCredentials = configuration.getCorsAllowCredentials(); boolean iiifAllowCredentials = configuration.getIiifAllowCredentials(); - if (corsAllowedOrigins != null) { - addCorsMapping(registry, "/api/**", corsAllowedOrigins, corsAllowCredentials); - addCorsMapping(registry, actuatorConfiguration.getActuatorBasePath() + "/**", - corsAllowedOrigins, corsAllowCredentials); + registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + // for our Access-Control-Allow-Origin header + .allowCredentials(corsAllowCredentials).allowedOrigins(corsAllowedOrigins) + // 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") + // 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 (iiifAllowedOrigins != null) { - addCorsMapping(registry, "/iiif/**", iiifAllowedOrigins, iiifAllowCredentials); + registry.addMapping("/iiif/**").allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + .allowCredentials(iiifAllowCredentials).allowedOrigins(iiifAllowedOrigins) + // 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") + // Allow list of response headers allowed to be sent by us (the server) to the client + .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); } - - } - - private void addCorsMapping(CorsRegistry registry, String pathPattern, - String[] allowedOrigins, boolean allowCredentials) { - - registry.addMapping(pathPattern).allowedMethods(CorsConfiguration.ALL) - // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid - // for our Access-Control-Allow-Origin header - .allowCredentials(allowCredentials).allowedOrigins(allowedOrigins) - // 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") - // Allow list of response headers allowed to be sent by us (the server) to the client - .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); } /** diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4c0d04dbff..e1e3dec830 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1583,6 +1583,12 @@ management.endpoint.health.status.order= down, out-of-service, up-with-issues, u management.health.ping.enabled = false management.health.diskSpace.enabled = false +management.endpoints.web.cors.allowed-origins = ${rest.cors.allowed-origins} +management.endpoints.web.cors.allowed-methods = * +management.endpoints.web.cors.allowed-headers = Accept, Authorization, Content-Type, Origin, X-On-Behalf-Of, X-Requested-With, X-XSRF-TOKEN, X-CORRELATION-ID, X-REFERRER +management.endpoints.web.cors.exposed-headers = Authorization, DSPACE-XSRF-TOKEN, Location, WWW-Authenticate +management.endpoints.web.cors.allow-credentials = true + #------------------------------------------------------------------# #-------------------MODULE CONFIGURATIONS--------------------------# #------------------------------------------------------------------# From d4f95a31bde4f45c75f196d66961436dcec85bee Mon Sep 17 00:00:00 2001 From: eskander Date: Wed, 30 Mar 2022 17:02:28 +0200 Subject: [PATCH 0784/1254] [CST-5306] Migrate Researcher Profile (REST). added ClaimItemFeature. --- .../dspace/app/profile/ResearcherProfile.java | 5 - .../profile/ResearcherProfileServiceImpl.java | 17 -- .../AfterResearcherProfileCreationAction.java | 35 --- .../impl/CanClaimItemFeature.java | 71 +++++++ .../impl/ShowClaimItemFeature.java | 93 ++++++++ .../converter/ResearcherProfileConverter.java | 50 ----- .../rest/login/impl/LoginEventFireAction.java | 46 ---- .../app/rest/model/ResearcherProfileRest.java | 74 ------- .../ResearcherProfileRestRepositoryIT.java | 7 - .../authorization/CanClaimItemFeatureIT.java | 198 +++++++++++++++++ .../authorization/ShowClaimItemFeatureIT.java | 199 ++++++++++++++++++ .../spring/rest/post-logged-in-actions.xml | 3 - 12 files changed, 561 insertions(+), 237 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/app/profile/service/AfterResearcherProfileCreationAction.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanClaimItemFeature.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ShowClaimItemFeature.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/LoginEventFireAction.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanClaimItemFeatureIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ShowClaimItemFeatureIT.java diff --git a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java index 59f4a0cfff..d282f0cb47 100644 --- a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java +++ b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java @@ -62,11 +62,6 @@ public class ResearcherProfile { return item; } -// public Optional getOrcid() { -// return getMetadataValue(item, "person.identifier.orcid") -// .map(metadataValue -> metadataValue.getValue()); -// } - private MetadataValue getDspaceObjectOwnerMetadata(Item item) { return getMetadataValue(item, "dspace.object.owner") .filter(metadata -> UUIDUtils.fromString(metadata.getAuthority()) != null) diff --git a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java index 8b9964972c..1e81636ff6 100644 --- a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java @@ -25,7 +25,6 @@ import javax.annotation.PostConstruct; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.dspace.app.exception.ResourceConflictException; -import org.dspace.app.profile.service.AfterResearcherProfileCreationAction; import org.dspace.app.profile.service.ResearcherProfileService; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -88,18 +87,6 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { @Autowired private AuthorizeService authorizeService; - @Autowired(required = false) - private List afterCreationActions; - - @PostConstruct - public void postConstruct() { - - if (afterCreationActions == null) { - afterCreationActions = Collections.emptyList(); - } - - } - @Override public ResearcherProfile findById(Context context, UUID id) throws SQLException, AuthorizeException { Assert.notNull(id, "An id must be provided to find a researcher profile"); @@ -133,10 +120,6 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { ResearcherProfile researcherProfile = new ResearcherProfile(item); - for (AfterResearcherProfileCreationAction afterCreationAction : afterCreationActions) { - afterCreationAction.perform(context, researcherProfile, ePerson); - } - return researcherProfile; } diff --git a/dspace-api/src/main/java/org/dspace/app/profile/service/AfterResearcherProfileCreationAction.java b/dspace-api/src/main/java/org/dspace/app/profile/service/AfterResearcherProfileCreationAction.java deleted file mode 100644 index 7bf5c97aa8..0000000000 --- a/dspace-api/src/main/java/org/dspace/app/profile/service/AfterResearcherProfileCreationAction.java +++ /dev/null @@ -1,35 +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.profile.service; - -import org.dspace.app.profile.ResearcherProfile; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; - -import java.sql.SQLException; - -/** - * Interface to mark classes that allow to perform additional logic on created - * researcher profile. - * - * @author Luca Giamminonni (luca.giamminonni at 4science.it) - * - */ -public interface AfterResearcherProfileCreationAction { - - /** - * Perform some actions on the given researcher profile and returns the updated - * profile. - * - * @param context the DSpace context - * @param researcherProfile the created researcher profile - * @param owner the EPerson that is owner of the given profile - * @throws SQLException if a SQL error occurs - */ - void perform(Context context, ResearcherProfile researcherProfile, EPerson owner) throws SQLException; -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanClaimItemFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanClaimItemFeature.java new file mode 100644 index 0000000000..f203247c36 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanClaimItemFeature.java @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization.impl; + +import java.sql.SQLException; +import java.util.Objects; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +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.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Checks if the given user can claim the given item. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + * + */ +@Component +@AuthorizationFeatureDocumentation(name = CanClaimItemFeature.NAME, + description = "Used to verify if the given user can request the claim of an item") +public class CanClaimItemFeature implements AuthorizationFeature { + + public static final String NAME = "canClaimItem"; + + @Autowired + private ItemService itemService; + + @Autowired + private ShowClaimItemFeature showClaimItemFeature; + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + + if (!showClaimItemFeature.isAuthorized(context, object)) { + return false; + } + + if (!(object instanceof ItemRest) || Objects.isNull(context.getCurrentUser())) { + return false; + } + + String id = ((ItemRest) object).getId(); + Item item = itemService.find(context, UUID.fromString(id)); + + return hasNotOwner(item); + } + + private boolean hasNotOwner(Item item) { + return StringUtils.isBlank(itemService.getMetadata(item, "dspace.object.owner")); + } + + @Override + public String[] getSupportedTypes() { + return new String[] { ItemRest.CATEGORY + "." + ItemRest.NAME }; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ShowClaimItemFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ShowClaimItemFeature.java new file mode 100644 index 0000000000..1de02ce331 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/ShowClaimItemFeature.java @@ -0,0 +1,93 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 java.util.Objects; +import java.util.UUID; + +import org.apache.commons.lang3.ArrayUtils; +import org.dspace.app.profile.service.ResearcherProfileService; +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.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Checks if the given user can request the claim of an item. Whether or not the + * user can then make the claim is determined by the feature + * {@link CanClaimItemFeature}. + * + * @author Corrado Lombardi (corrado.lombardi at 4science.it) + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + */ +@Component +@AuthorizationFeatureDocumentation(name = ShowClaimItemFeature.NAME, + description = "Used to verify if the given user can request the claim of an item") +public class ShowClaimItemFeature implements AuthorizationFeature { + + public static final String NAME = "showClaimItem"; + private static final Logger LOG = LoggerFactory.getLogger(ShowClaimItemFeature.class); + + private final ItemService itemService; + private final ResearcherProfileService researcherProfileService; + private final ConfigurationService configurationService; + + @Autowired + public ShowClaimItemFeature(ItemService itemService, + ResearcherProfileService researcherProfileService, + ConfigurationService configurationService) { + this.itemService = itemService; + this.researcherProfileService = researcherProfileService; + this.configurationService = configurationService; + } + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + + if (!(object instanceof ItemRest) || Objects.isNull(context.getCurrentUser())) { + return false; + } + + String id = ((ItemRest) object).getId(); + Item item = itemService.find(context, UUID.fromString(id)); + + return claimableEntityType(item) && hasNotAlreadyAProfile(context); + } + + private boolean hasNotAlreadyAProfile(Context context) { + try { + return researcherProfileService.findById(context, context.getCurrentUser().getID()) == null; + } catch (SQLException | AuthorizeException e) { + LOG.warn("Error while checking if eperson has a ResearcherProfileAssociated: {}", + e.getMessage(), e); + return false; + } + } + + private boolean claimableEntityType(Item item) { + String[] claimableEntityTypes = configurationService.getArrayProperty("claimable.entityType"); + String entityType = itemService.getEntityType(item); + return ArrayUtils.contains(claimableEntityTypes, entityType); + } + + @Override + public String[] getSupportedTypes() { + return new String[] {ItemRest.CATEGORY + "." + ItemRest.NAME}; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java index 4eb291d8d1..7074065105 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java @@ -7,22 +7,10 @@ */ package org.dspace.app.rest.converter; -//import static org.dspace.app.orcid.model.OrcidEntityType.FUNDING; -//import static org.dspace.app.orcid.model.OrcidEntityType.PUBLICATION; - -import java.util.List; -import java.util.stream.Collectors; - -//import org.dspace.app.orcid.service.OrcidSynchronizationService; -//import org.dspace.app.profile.OrcidEntitySyncPreference; -//import org.dspace.app.profile.OrcidProfileSyncPreference; -//import org.dspace.app.profile.OrcidSynchronizationMode; import org.dspace.app.profile.ResearcherProfile; import org.dspace.app.rest.model.ResearcherProfileRest; -//import org.dspace.app.rest.model.ResearcherProfileRest.OrcidSynchronizationRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.Item; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** @@ -35,9 +23,6 @@ import org.springframework.stereotype.Component; @Component public class ResearcherProfileConverter implements DSpaceConverter { -// @Autowired -// private OrcidSynchronizationService orcidSynchronizationService; - @Override public ResearcherProfileRest convert(ResearcherProfile profile, Projection projection) { ResearcherProfileRest researcherProfileRest = new ResearcherProfileRest(); @@ -48,44 +33,9 @@ public class ResearcherProfileConverter implements DSpaceConverter getProfilePreferences(Item item) { -// return orcidSynchronizationService.getProfilePreferences(item).stream() -// .map(OrcidProfileSyncPreference::name) -// .collect(Collectors.toList()); -// } -// -// private String getMode(Item item) { -// return orcidSynchronizationService.getSynchronizationMode(item) -// .map(OrcidSynchronizationMode::name) -// .orElse(OrcidSynchronizationMode.MANUAL.name()); -// } - @Override public Class getModelClass() { return ResearcherProfile.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/LoginEventFireAction.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/LoginEventFireAction.java deleted file mode 100644 index b5df891d10..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/LoginEventFireAction.java +++ /dev/null @@ -1,46 +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.login.impl; - -import org.dspace.app.rest.login.PostLoggedInAction; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -import org.dspace.services.EventService; -import org.dspace.usage.UsageEvent; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; - -/** - * Implementation of {@link PostLoggedInAction} that fire an LOGIN event. - * - * @author Luca Giamminonni (luca.giamminonni at 4science.it) - * - */ -public class LoginEventFireAction implements PostLoggedInAction { - - @Autowired - private EventService eventService; - - @Override - public void loggedIn(Context context) { - - HttpServletRequest request = getCurrentRequest(); - EPerson currentUser = context.getCurrentUser(); - - eventService.fireEvent(new UsageEvent(UsageEvent.Action.LOGIN, request, context, currentUser)); - - } - - private HttpServletRequest getCurrentRequest() { - return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java index c1d8f69254..ff789f9323 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java @@ -27,7 +27,6 @@ import java.util.UUID; public class ResearcherProfileRest extends BaseObjectRest { private static final long serialVersionUID = 1L; - // changed from RestModel.CRIS to RestModel.EPERSON public static final String CATEGORY = RestModel.EPERSON; public static final String NAME = "profile"; @@ -36,12 +35,6 @@ public class ResearcherProfileRest extends BaseObjectRest { private boolean visible; -// @JsonInclude(Include.NON_NULL) -// private String orcid; - -// @JsonInclude(Include.NON_NULL) -// private OrcidSynchronizationRest orcidSynchronization; - public boolean isVisible() { return visible; } @@ -50,22 +43,6 @@ public class ResearcherProfileRest extends BaseObjectRest { this.visible = visible; } -// public OrcidSynchronizationRest getOrcidSynchronization() { -// return orcidSynchronization; -// } - -// public void setOrcidSynchronization(OrcidSynchronizationRest orcidSynchronization) { -// this.orcidSynchronization = orcidSynchronization; -// } - -// public String getOrcid() { -// return orcid; -// } -// -// public void setOrcid(String orcid) { -// this.orcid = orcid; -// } - @Override public String getType() { return NAME; @@ -80,55 +57,4 @@ public class ResearcherProfileRest extends BaseObjectRest { public Class getController() { return RestResourceController.class; } - - /** - * Inner class to model ORCID synchronization preferences and mode. - * - * @author Luca Giamminonni (luca.giamminonni at 4science.it) - * - */ -// public static class OrcidSynchronizationRest { -// -// private String mode; -// -// private String publicationsPreference; -// -// private String fundingsPreference; -// -// private List profilePreferences; -// -// public String getMode() { -// return mode; -// } -// -// public void setMode(String mode) { -// this.mode = mode; -// } -// -// public List getProfilePreferences() { -// return profilePreferences; -// } -// -// public void setProfilePreferences(List profilePreferences) { -// this.profilePreferences = profilePreferences; -// } -// -// public String getPublicationsPreference() { -// return publicationsPreference; -// } -// -// public void setPublicationsPreference(String publicationsPreference) { -// this.publicationsPreference = publicationsPreference; -// } -// -// public String getFundingsPreference() { -// return fundingsPreference; -// } -// -// public void setFundingsPreference(String fundingsPreference) { -// this.fundingsPreference = fundingsPreference; -// } -// -// } - } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java index f5a460b298..b316ea3acd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java @@ -114,9 +114,6 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra administrators = groupService.findByName(context, Group.ADMIN); -// itemService.addMetadata(context, personCollection.getTemplateItem(), "cris", "policy", -// "group", null, administrators.getName()); - configurationService.setProperty("researcher-profile.collection.uuid", personCollection.getID().toString()); configurationService.setProperty("claimable.entityType", "Person"); @@ -269,8 +266,6 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("item"))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id.toString(), 0))) - //.andExpect(jsonPath("$.metadata", matchMetadata("cris.policy.group", administrators.getName(), -// UUIDUtils.toString(administrators.getID()), 0))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) @@ -311,8 +306,6 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("item"))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id.toString(), 0))) -// .andExpect(jsonPath("$.metadata", matchMetadata("cris.policy.group", administrators.getName(), -// UUIDUtils.toString(administrators.getID()), 0))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanClaimItemFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanClaimItemFeatureIT.java new file mode 100644 index 0000000000..368d0f1a44 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanClaimItemFeatureIT.java @@ -0,0 +1,198 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +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.authorization.impl.CanClaimItemFeature; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test of Profile Claim Authorization Feature implementation. + * + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + */ +public class CanClaimItemFeatureIT extends AbstractControllerIntegrationTest { + + private Item collectionAProfile; + private Item collectionBProfile; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private ItemConverter itemConverter; + + @Autowired + private Utils utils; + + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + private AuthorizationFeature canClaimProfileFeature; + + private Collection personCollection; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); + personCollection = + CollectionBuilder.createCollection(context, parentCommunity).withEntityType("Person") + .withName("claimableA").build(); + final Collection claimableCollectionB = + CollectionBuilder.createCollection(context, parentCommunity).withEntityType("Person") + .withName("claimableB").build(); + + collectionAProfile = ItemBuilder.createItem(context, personCollection).build(); + collectionBProfile = ItemBuilder.createItem(context, claimableCollectionB).build(); + + configurationService.addPropertyValue("claimable.entityType", "Person"); + + context.restoreAuthSystemState(); + + canClaimProfileFeature = authorizationFeatureService.find(CanClaimItemFeature.NAME); + + } + + @Test + public void testCanClaimAProfile() throws Exception { + + String token = getAuthToken(context.getCurrentUser().getEmail(), password); + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(collectionAProfile)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", canClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(collectionBProfile)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", canClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + } + + @Test + public void testNotClaimableEntity() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection publicationCollection = CollectionBuilder + .createCollection(context, parentCommunity) + .withEntityType("Publication") + .withName("notClaimable") + .build(); + + context.turnOffAuthorisationSystem(); + + Item publication = ItemBuilder.createItem(context, publicationCollection).build(); + + String token = getAuthToken(context.getCurrentUser().getEmail(), password); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(publication)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", canClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", equalTo(0))); + + } + + @Test + public void testItemAlreadyInARelation() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item ownedItem = ItemBuilder.createItem(context, personCollection) + .withDspaceObjectOwner("owner", "ownerAuthority").build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(context.getCurrentUser().getEmail(), password); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(ownedItem)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", canClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", equalTo(0))); + + } + + @Test + public void testUserWithProfile() throws Exception { + + context.turnOffAuthorisationSystem(); + + ItemBuilder.createItem(context, personCollection) + .withTitle("User") + .withDspaceObjectOwner("User", context.getCurrentUser().getID().toString()) + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(context.getCurrentUser().getEmail(), password)) + .perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(collectionAProfile)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", canClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", equalTo(0))); + } + + @Test + public void testWithoutClaimableEntities() throws Exception { + + configurationService.setProperty("claimable.entityType", null); + + getClient(getAuthToken(context.getCurrentUser().getEmail(), password)) + .perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(collectionAProfile)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", canClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", equalTo(0))); + + } + + private String uri(Item item) { + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); + String itemRestURI = utils.linkToSingleResource(itemRest, "self").getHref(); + return itemRestURI; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ShowClaimItemFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ShowClaimItemFeatureIT.java new file mode 100644 index 0000000000..563e443efe --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ShowClaimItemFeatureIT.java @@ -0,0 +1,199 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +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.authorization.impl.ShowClaimItemFeature; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test of Show Claim Item Authorization Feature implementation. + * + * @author Corrado Lombardi (corrado.lombardi at 4science.it) + * @author Luca Giamminonni (luca.giamminonni at 4science.it) + */ +public class ShowClaimItemFeatureIT extends AbstractControllerIntegrationTest { + + private Item collectionAProfile; + private Item collectionBProfile; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private ItemConverter itemConverter; + + @Autowired + private Utils utils; + + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + private AuthorizationFeature showClaimProfileFeature; + + private Collection personCollection; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); + personCollection = + CollectionBuilder.createCollection(context, parentCommunity).withEntityType("Person") + .withName("claimableA").build(); + final Collection claimableCollectionB = + CollectionBuilder.createCollection(context, parentCommunity).withEntityType("Person") + .withName("claimableB").build(); + + collectionAProfile = ItemBuilder.createItem(context, personCollection).build(); + collectionBProfile = ItemBuilder.createItem(context, claimableCollectionB).build(); + + configurationService.addPropertyValue("claimable.entityType", "Person"); + + context.restoreAuthSystemState(); + + showClaimProfileFeature = authorizationFeatureService.find(ShowClaimItemFeature.NAME); + + } + + @Test + public void testCanClaimAProfile() throws Exception { + + String token = getAuthToken(context.getCurrentUser().getEmail(), password); + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(collectionAProfile)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", showClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(collectionBProfile)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", showClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + } + + @Test + public void testNotClaimableEntity() throws Exception { + + context.turnOffAuthorisationSystem(); + + Collection publicationCollection = CollectionBuilder + .createCollection(context, parentCommunity) + .withEntityType("Publication") + .withName("notClaimable") + .build(); + + context.turnOffAuthorisationSystem(); + + Item publication = ItemBuilder.createItem(context, publicationCollection).build(); + + String token = getAuthToken(context.getCurrentUser().getEmail(), password); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(publication)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", showClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", equalTo(0))); + + } + + @Test + public void testItemAlreadyInARelation() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item ownedItem = ItemBuilder.createItem(context, personCollection) + .withDspaceObjectOwner("owner", "ownerAuthority").build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(context.getCurrentUser().getEmail(), password); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(ownedItem)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", showClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + + } + + @Test + public void testUserWithProfile() throws Exception { + + context.turnOffAuthorisationSystem(); + + ItemBuilder.createItem(context, personCollection) + .withTitle("User") + .withDspaceObjectOwner("User", context.getCurrentUser().getID().toString()) + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(context.getCurrentUser().getEmail(), password)) + .perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(collectionAProfile)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", showClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", equalTo(0))); + } + + @Test + public void testWithoutClaimableEntities() throws Exception { + + configurationService.setProperty("claimable.entityType", null); + + getClient(getAuthToken(context.getCurrentUser().getEmail(), password)) + .perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(collectionAProfile)) + .param("eperson", context.getCurrentUser().getID().toString()) + .param("feature", showClaimProfileFeature.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", equalTo(0))); + + } + + private String uri(Item item) { + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); + String itemRestURI = utils.linkToSingleResource(itemRest, "self").getHref(); + return itemRestURI; + } + +} diff --git a/dspace/config/spring/rest/post-logged-in-actions.xml b/dspace/config/spring/rest/post-logged-in-actions.xml index e486a205ac..1e02569647 100644 --- a/dspace/config/spring/rest/post-logged-in-actions.xml +++ b/dspace/config/spring/rest/post-logged-in-actions.xml @@ -9,7 +9,4 @@ - - - \ No newline at end of file From 70f85edf9ebfd8b85531a7f71eca12f222169e8b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 5 Jan 2022 10:12:01 -0600 Subject: [PATCH 0785/1254] Upgrade to h2 v2.x. Requires also updating to Hibernate 5.6.x --- dspace-api/pom.xml | 2 +- .../rdbms/migration/MigrationUtils.java | 7 +-- .../h2/V1.4__Upgrade_to_DSpace_1.4_schema.sql | 6 +-- ...9.26__DS-1582_Metadata_For_All_Objects.sql | 44 +++++++++---------- .../h2/V5.6_2016.08.23__DS-3097.sql | 4 +- ...125-fix-bundle-bitstream-delete-rights.sql | 2 +- .../h2/V6.0_2016.08.23__DS-3097.sql | 4 +- ...DV_place_after_migrating_from_DSpace_5.sql | 3 +- dspace-iiif/pom.xml | 7 +++ dspace-server-webapp/pom.xml | 7 +++ pom.xml | 13 ++++-- 11 files changed, 61 insertions(+), 38 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 6a54a96376..d3085ecdec 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -373,7 +373,7 @@ org.hibernate.javax.persistence hibernate-jpa-2.1-api - 1.0.0.Final + 1.0.2.Final diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java index 624d0cb55a..842fc15e16 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/migration/MigrationUtils.java @@ -86,10 +86,11 @@ public class MigrationUtils { cascade = true; break; case "h2": - // In H2, constraints are listed in the "information_schema.constraints" table + // In H2, column constraints are listed in the "INFORMATION_SCHEMA.KEY_COLUMN_USAGE" table constraintNameSQL = "SELECT DISTINCT CONSTRAINT_NAME " + - "FROM information_schema.constraints " + - "WHERE table_name = ? AND column_list = ?"; + "FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE " + + "WHERE TABLE_NAME = ? AND COLUMN_NAME = ?"; + cascade = true; break; default: throw new SQLException("DBMS " + dbtype + " is unsupported in this migration."); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V1.4__Upgrade_to_DSpace_1.4_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V1.4__Upgrade_to_DSpace_1.4_schema.sql index e00a651626..62d12fe5ce 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V1.4__Upgrade_to_DSpace_1.4_schema.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V1.4__Upgrade_to_DSpace_1.4_schema.sql @@ -245,13 +245,13 @@ insert into most_recent_checksum ) select bitstream.bitstream_id, - '1', + true, CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, FORMATDATETIME(NOW(),'DD-MM-RRRR HH24:MI:SS'), FORMATDATETIME(NOW(),'DD-MM-RRRR HH24:MI:SS'), CASE WHEN bitstream.checksum_algorithm IS NULL THEN 'MD5' ELSE bitstream.checksum_algorithm END, - '1' + true from bitstream; -- Update all the deleted checksums @@ -263,7 +263,7 @@ update most_recent_checksum set to_be_processed = 0 where most_recent_checksum.bitstream_id in ( select bitstream_id -from bitstream where deleted = '1' ); +from bitstream where deleted = true ); -- this will insert into history table -- for the initial start diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql index 87551bdf4e..cd908279f1 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V5.0_2014.09.26__DS-1582_Metadata_For_All_Objects.sql @@ -36,7 +36,7 @@ alter table metadatavalue alter column resource_id set not null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, community_id AS resource_id, 4 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, @@ -47,7 +47,7 @@ FROM community where not introductory_text is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, community_id AS resource_id, 4 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'abstract') AS metadata_field_id, @@ -58,7 +58,7 @@ FROM community where not short_description is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, community_id AS resource_id, 4 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'tableofcontents') AS metadata_field_id, @@ -69,7 +69,7 @@ FROM community where not side_bar_text is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, community_id AS resource_id, 4 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier is null) AS metadata_field_id, @@ -80,7 +80,7 @@ FROM community where not copyright_text is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, community_id AS resource_id, 4 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, @@ -104,7 +104,7 @@ alter table community drop column name; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, collection_id AS resource_id, 3 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, @@ -115,7 +115,7 @@ FROM collection where not introductory_text is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, collection_id AS resource_id, 3 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'abstract') AS metadata_field_id, @@ -126,7 +126,7 @@ FROM collection where not short_description is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, collection_id AS resource_id, 3 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier = 'tableofcontents') AS metadata_field_id, @@ -137,7 +137,7 @@ FROM collection where not side_bar_text is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, collection_id AS resource_id, 3 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier is null) AS metadata_field_id, @@ -148,7 +148,7 @@ FROM collection where not copyright_text is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, collection_id AS resource_id, 3 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, @@ -159,7 +159,7 @@ FROM collection where not name is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, collection_id AS resource_id, 3 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'provenance' and qualifier is null) AS metadata_field_id, @@ -170,7 +170,7 @@ FROM collection where not provenance_description is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, collection_id AS resource_id, 3 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'rights' and qualifier = 'license') AS metadata_field_id, @@ -194,7 +194,7 @@ alter table collection drop column provenance_description; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, bundle_id AS resource_id, 1 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, @@ -214,7 +214,7 @@ alter table bundle drop column name; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, bitstream_id AS resource_id, 0 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, @@ -225,7 +225,7 @@ FROM bitstream where not name is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, bitstream_id AS resource_id, 0 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'description' and qualifier is null) AS metadata_field_id, @@ -236,7 +236,7 @@ FROM bitstream where not description is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, bitstream_id AS resource_id, 0 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'format' and qualifier is null) AS metadata_field_id, @@ -247,7 +247,7 @@ FROM bitstream where not user_format_description is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, bitstream_id AS resource_id, 0 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'source' and qualifier is null) AS metadata_field_id, @@ -269,7 +269,7 @@ alter table bitstream drop column source; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, eperson_group_id AS resource_id, 6 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='dc') and element = 'title' and qualifier is null) AS metadata_field_id, @@ -288,7 +288,7 @@ alter table epersongroup drop column name; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, eperson_id AS resource_id, 7 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'firstname' and qualifier is null) AS metadata_field_id, @@ -299,7 +299,7 @@ FROM eperson where not firstname is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, eperson_id AS resource_id, 7 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'lastname' and qualifier is null) AS metadata_field_id, @@ -310,7 +310,7 @@ FROM eperson where not lastname is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, eperson_id AS resource_id, 7 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'phone' and qualifier is null) AS metadata_field_id, @@ -321,7 +321,7 @@ FROM eperson where not phone is null; INSERT INTO metadatavalue (metadata_value_id, resource_id, resource_type_id, metadata_field_id, text_value, text_lang, place) SELECT -metadatavalue_seq.nextval as metadata_value_id, +NEXT VALUE FOR metadatavalue_seq as metadata_value_id, eperson_id AS resource_id, 7 AS resource_type_id, (select metadata_field_id from metadatafieldregistry where metadata_schema_id=(select metadata_schema_id from metadataschemaregistry where short_id='eperson') and element = 'language' and qualifier is null) AS metadata_field_id, diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V5.6_2016.08.23__DS-3097.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V5.6_2016.08.23__DS-3097.sql index 2e09b807de..0bd68c5201 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V5.6_2016.08.23__DS-3097.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V5.6_2016.08.23__DS-3097.sql @@ -14,11 +14,11 @@ UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and resource_type_i SELECT bundle2bitstream.bitstream_id FROM bundle2bitstream LEFT JOIN item2bundle ON bundle2bitstream.bundle_id = item2bundle.bundle_id LEFT JOIN item ON item2bundle.item_id = item.item_id - WHERE item.withdrawn = 1 + WHERE item.withdrawn = true ); UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and resource_type_id = 1 and resource_id in ( SELECT item2bundle.bundle_id FROM item2bundle LEFT JOIN item ON item2bundle.item_id = item.item_id - WHERE item.withdrawn = 1 + WHERE item.withdrawn = true ); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql index 1c98ceef2a..1ee23246ea 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.04.14__DS-3125-fix-bundle-bitstream-delete-rights.sql @@ -17,7 +17,7 @@ INSERT INTO resourcepolicy (policy_id, resource_type_id, resource_id, action_id, start_date, end_date, rpname, rptype, rpdescription, eperson_id, epersongroup_id, dspace_object) SELECT -resourcepolicy_seq.nextval AS policy_id, +NEXT VALUE FOR resourcepolicy_seq AS policy_id, resource_type_id, resource_id, -- Insert the Constants.DELETE action diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.08.23__DS-3097.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.08.23__DS-3097.sql index e1220c8c7c..5bb59970c5 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.08.23__DS-3097.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2016.08.23__DS-3097.sql @@ -14,11 +14,11 @@ UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and dspace_object i SELECT bundle2bitstream.bitstream_id FROM bundle2bitstream LEFT JOIN item2bundle ON bundle2bitstream.bundle_id = item2bundle.bundle_id LEFT JOIN item ON item2bundle.item_id = item.uuid - WHERE item.withdrawn = 1 + WHERE item.withdrawn = true ); UPDATE resourcepolicy SET action_id = 12 where action_id = 0 and dspace_object in ( SELECT item2bundle.bundle_id FROM item2bundle LEFT JOIN item ON item2bundle.item_id = item.uuid - WHERE item.withdrawn = 1 + WHERE item.withdrawn = true ); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql index 3b649a321c..7506433cdd 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.1_2021.10.18__Fix_MDV_place_after_migrating_from_DSpace_5.sql @@ -9,10 +9,11 @@ ---------------------------------------------------- -- Make sure the metadatavalue.place column starts at 0 instead of 1 ---------------------------------------------------- + CREATE LOCAL TEMPORARY TABLE mdv_minplace ( dspace_object_id UUID NOT NULL, metadata_field_id INT NOT NULL, - minplace INT NOT NULL, + minplace INT NOT NULL ); INSERT INTO mdv_minplace diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index c8f64c6f04..e606523182 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -45,6 +45,13 @@ org.springframework.boot spring-boot-starter-web ${spring-boot.version} + + + + org.hibernate.validator + hibernate-validator + + org.springframework.boot diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 20e6602347..f90ea51b15 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -260,6 +260,13 @@ org.springframework.boot spring-boot-starter-web ${spring-boot.version} + + + + org.hibernate.validator + hibernate-validator + + org.springframework.boot diff --git a/pom.xml b/pom.xml index e03dcb3b07..f050e1e36f 100644 --- a/pom.xml +++ b/pom.xml @@ -22,8 +22,8 @@ 5.2.5.RELEASE 2.2.6.RELEASE 5.2.2.RELEASE - 5.4.10.Final - 6.0.18.Final + 5.6.5.Final + 6.0.23.Final 42.3.3 8.8.1 @@ -1161,6 +1161,13 @@ ${hibernate-validator.version} + + + org.jboss.logging + jboss-logging + 3.4.3.Final + + com.rometools @@ -1691,7 +1698,7 @@ com.h2database h2 - 1.4.187 + 2.1.210 test From a35a6d4a28a6a494b5d9f2855109bc004bc943d8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 5 Jan 2022 10:12:32 -0600 Subject: [PATCH 0786/1254] Upgrade to Flyway 8.2.x to support h2 v2 --- dspace-api/pom.xml | 2 +- .../storage/rdbms/EntityTypeServiceInitializer.java | 10 ++++++++++ .../dspace/storage/rdbms/GroupServiceInitializer.java | 10 ++++++++++ .../dspace/storage/rdbms/PostgreSQLCryptoChecker.java | 10 ++++++++++ .../java/org/dspace/storage/rdbms/RegistryUpdater.java | 10 ++++++++++ .../dspace/storage/rdbms/SiteServiceInitializer.java | 10 ++++++++++ 6 files changed, 51 insertions(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index d3085ecdec..572b9f8cf3 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -762,7 +762,7 @@ org.flywaydb flyway-core - 6.5.7 + 8.2.3 diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/EntityTypeServiceInitializer.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/EntityTypeServiceInitializer.java index ebf790900b..e0e41516d0 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/EntityTypeServiceInitializer.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/EntityTypeServiceInitializer.java @@ -49,6 +49,16 @@ public class EntityTypeServiceInitializer implements Callback { } } + /** + * The callback name, Flyway will use this to sort the callbacks alphabetically before executing them + * @return The callback name + */ + @Override + public String getCallbackName() { + // Return class name only (not prepended by package) + return EntityTypeServiceInitializer.class.getSimpleName(); + } + @Override public boolean supports(Event event, org.flywaydb.core.api.callback.Context context) { // Must run AFTER all migrations complete, since it is dependent on Hibernate diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/GroupServiceInitializer.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/GroupServiceInitializer.java index 7338dd75bc..54498a1c64 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/GroupServiceInitializer.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/GroupServiceInitializer.java @@ -51,6 +51,16 @@ public class GroupServiceInitializer implements Callback { } + /** + * The callback name, Flyway will use this to sort the callbacks alphabetically before executing them + * @return The callback name + */ + @Override + public String getCallbackName() { + // Return class name only (not prepended by package) + return GroupServiceInitializer.class.getSimpleName(); + } + /** * Events supported by this callback. * @param event Flyway event diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/PostgreSQLCryptoChecker.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/PostgreSQLCryptoChecker.java index 5798f4254c..5459cc3cc3 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/PostgreSQLCryptoChecker.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/PostgreSQLCryptoChecker.java @@ -97,6 +97,16 @@ public class PostgreSQLCryptoChecker implements Callback { } } + /** + * The callback name, Flyway will use this to sort the callbacks alphabetically before executing them + * @return The callback name + */ + @Override + public String getCallbackName() { + // Return class name only (not prepended by package) + return PostgreSQLCryptoChecker.class.getSimpleName(); + } + /** * Events supported by this callback. * @param event Flyway event diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/RegistryUpdater.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/RegistryUpdater.java index ae8be0988a..c2a9b8a848 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/RegistryUpdater.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/RegistryUpdater.java @@ -101,6 +101,16 @@ public class RegistryUpdater implements Callback { } + /** + * The callback name, Flyway will use this to sort the callbacks alphabetically before executing them + * @return The callback name + */ + @Override + public String getCallbackName() { + // Return class name only (not prepended by package) + return RegistryUpdater.class.getSimpleName(); + } + /** * Events supported by this callback. * @param event Flyway event diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/SiteServiceInitializer.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/SiteServiceInitializer.java index 26e76804e1..872a633146 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/SiteServiceInitializer.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/SiteServiceInitializer.java @@ -73,6 +73,16 @@ public class SiteServiceInitializer implements Callback { } + /** + * The callback name, Flyway will use this to sort the callbacks alphabetically before executing them + * @return The callback name + */ + @Override + public String getCallbackName() { + // Return class name only (not prepended by package) + return SiteServiceInitializer.class.getSimpleName(); + } + /** * Events supported by this callback. * @param event Flyway event From 5a046dca13e8a4c60ad5e4deef27e0fd40adc0ad Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 5 Jan 2022 10:21:25 -0600 Subject: [PATCH 0787/1254] Fix incorrect StringUtils import --- .../org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java index a2ed672a61..3295e03593 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java @@ -22,13 +22,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.sql.SQLException; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.matcher.EntityTypeMatcher; import org.dspace.app.rest.matcher.RelationshipTypeMatcher; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.content.RelationshipType; import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.RelationshipTypeService; -import org.h2.util.StringUtils; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; From 531fc8acda9101e16fa7c327a152e2aa5798be49 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 24 Feb 2022 16:38:14 -0600 Subject: [PATCH 0788/1254] Upgrade to Flyway 8.4.4 --- dspace-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 572b9f8cf3..b8fae8d86c 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -762,7 +762,7 @@ org.flywaydb flyway-core - 8.2.3 + 8.4.4 From 0595eeddbb4a6d9fe2f008fa4fee7e27f4410008 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 16 Feb 2022 16:35:41 -0600 Subject: [PATCH 0789/1254] Tiny stability fix to test (fails on Windows). Remove newline from string comparison --- .../org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java b/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java index 502266da06..2711f46c31 100644 --- a/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java +++ b/dspace-api/src/test/java/org/dspace/iiif/canvasdimension/CanvasDimensionsIT.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; +import org.apache.commons.lang.StringUtils; import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; @@ -408,7 +409,7 @@ public class CanvasDimensionsIT extends AbstractIntegrationTestWithDatabase { execCanvasScriptWithMaxRecs(id); // check System.out for number of items processed. - assertEquals("2 IIIF items were processed.\n", outContent.toString()); + assertEquals("2 IIIF items were processed.", StringUtils.chomp(outContent.toString())); } @Test From c4ad834a8a8c3b14c5d8f533a14acc766a3c8b52 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 18 Feb 2022 14:04:04 -0600 Subject: [PATCH 0790/1254] Cleanup IT logs by loading beans by name. Add note to application.properties on how to enable debug logs --- .../org/dspace/AbstractIntegrationTestWithDatabase.java | 8 +++++--- .../src/main/resources/application.properties | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java index cada4944e3..e27fb19a68 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTestWithDatabase.java @@ -15,6 +15,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.authority.AuthoritySearchService; import org.dspace.authority.MockAuthoritySolrServiceImpl; import org.dspace.authorize.AuthorizeException; import org.dspace.builder.AbstractBuilder; @@ -31,6 +32,7 @@ import org.dspace.kernel.ServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.MockSolrLoggerServiceImpl; import org.dspace.statistics.MockSolrStatisticsCore; +import org.dspace.statistics.SolrStatisticsCore; import org.dspace.storage.rdbms.DatabaseUtils; import org.jdom2.Document; import org.junit.After; @@ -183,15 +185,15 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati searchService.reset(); // Clear the statistics core. serviceManager - .getServiceByName(null, MockSolrStatisticsCore.class) + .getServiceByName(SolrStatisticsCore.class.getName(), MockSolrStatisticsCore.class) .reset(); MockSolrLoggerServiceImpl statisticsService = serviceManager - .getServiceByName(null, MockSolrLoggerServiceImpl.class); + .getServiceByName("solrLoggerService", MockSolrLoggerServiceImpl.class); statisticsService.reset(); MockAuthoritySolrServiceImpl authorityService = serviceManager - .getServiceByName(null, MockAuthoritySolrServiceImpl.class); + .getServiceByName(AuthoritySearchService.class.getName(), MockAuthoritySolrServiceImpl.class); authorityService.reset(); // Reload our ConfigurationService (to reset configs to defaults again) diff --git a/dspace-server-webapp/src/main/resources/application.properties b/dspace-server-webapp/src/main/resources/application.properties index c695fe2fba..91e2aa73ae 100644 --- a/dspace-server-webapp/src/main/resources/application.properties +++ b/dspace-server-webapp/src/main/resources/application.properties @@ -117,6 +117,8 @@ spring.main.allow-bean-definition-overriding = true ######################### # Spring Boot Logging levels # +# NOTE: The below settings can be uncommented to debug issues in Spring Boot/WebMVC. +# These "logging.level" settings will also override defaults in "logging.config" below. #logging.level.org.springframework.boot=DEBUG #logging.level.org.springframework.web=DEBUG #logging.level.org.hibernate=ERROR From 57b19fa71a728a53bce120c6c3c5a18882c191b3 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 21 Dec 2021 09:12:52 -0600 Subject: [PATCH 0791/1254] Bug fix, ensure Solr container is *shutdown* when destroyed --- dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java b/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java index e80d5f8e17..aed0c088c3 100644 --- a/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java +++ b/dspace-api/src/test/java/org/dspace/solr/MockSolrServer.java @@ -171,6 +171,7 @@ public class MockSolrServer { * Discard the embedded Solr container. */ private static synchronized void destroyContainer() { + container.shutdown(); container = null; log.info("SOLR CoreContainer destroyed"); } From 62c0e28f5451dddb7b8f0c34c5af0a8bdca6168b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 1 Mar 2022 17:06:19 -0600 Subject: [PATCH 0792/1254] Remove custom Postgres Dialect. Replace with DatabaseAwareLobType --- .../org/dspace/authorize/ResourcePolicy.java | 2 +- .../org/dspace/content/MetadataValue.java | 2 +- .../rdbms/hibernate/DatabaseAwareLobType.java | 57 ++++++++++++++++ .../postgres/DSpacePostgreSQL82Dialect.java | 67 ------------------- dspace/config/dspace.cfg | 4 +- dspace/config/local.cfg.EXAMPLE | 4 +- 6 files changed, 63 insertions(+), 73 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java delete mode 100644 dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/postgres/DSpacePostgreSQL82Dialect.java diff --git a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java index a25a492a3a..954bb96990 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java +++ b/dspace-api/src/main/java/org/dspace/authorize/ResourcePolicy.java @@ -93,7 +93,7 @@ public class ResourcePolicy implements ReloadableEntity { private String rptype; @Lob - @Type(type = "org.hibernate.type.MaterializedClobType") + @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") @Column(name = "rpdescription") private String rpdescription; diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java index d1b636cdff..9ff3cb9ec2 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataValue.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataValue.java @@ -59,7 +59,7 @@ public class MetadataValue implements ReloadableEntity { * The value of the field */ @Lob - @Type(type = "org.hibernate.type.MaterializedClobType") + @Type(type = "org.dspace.storage.rdbms.hibernate.DatabaseAwareLobType") @Column(name = "text_value") private String value; diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java new file mode 100644 index 0000000000..95939f9902 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/DatabaseAwareLobType.java @@ -0,0 +1,57 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.storage.rdbms.hibernate; + +import org.apache.commons.lang.StringUtils; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.hibernate.type.AbstractSingleColumnStandardBasicType; +import org.hibernate.type.descriptor.java.StringTypeDescriptor; +import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; +import org.hibernate.type.descriptor.sql.LongVarcharTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +/** + * A Hibernate @Type used to properly support the CLOB in both Postgres and Oracle. + * PostgreSQL doesn't have a CLOB type, instead it's a TEXT field. + * Normally, you'd use org.hibernate.type.TextType to support TEXT, but that won't work for Oracle. + * https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/type/TextType.java + * + * This Type checks if we are using PostgreSQL. + * If so, it configures Hibernate to map CLOB to LongVarChar (same as org.hibernate.type.TextType) + * If not, it uses default CLOB (which works for other databases). + */ +public class DatabaseAwareLobType extends AbstractSingleColumnStandardBasicType { + + public static final DatabaseAwareLobType INSTANCE = new DatabaseAwareLobType(); + + public DatabaseAwareLobType() { + super( getDbDescriptor(), StringTypeDescriptor.INSTANCE ); + } + + public static SqlTypeDescriptor getDbDescriptor() { + if ( isPostgres() ) { + return LongVarcharTypeDescriptor.INSTANCE; + } else { + return ClobTypeDescriptor.DEFAULT; + } + } + + private static boolean isPostgres() { + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + String dbDialect = configurationService.getProperty("db.dialect"); + + return StringUtils.containsIgnoreCase(dbDialect, "PostgreSQL"); + } + + @Override + public String getName() { + return "database_aware_lob"; + } +} + diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/postgres/DSpacePostgreSQL82Dialect.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/postgres/DSpacePostgreSQL82Dialect.java deleted file mode 100644 index 2701c22fd2..0000000000 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/hibernate/postgres/DSpacePostgreSQL82Dialect.java +++ /dev/null @@ -1,67 +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.rdbms.hibernate.postgres; - -import java.sql.Types; - -import org.hibernate.dialect.PostgreSQL82Dialect; -import org.hibernate.service.ServiceRegistry; -import org.hibernate.type.PostgresUUIDType; -import org.hibernate.type.descriptor.sql.LongVarcharTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; - -/** - * UUID's are not supported by default in hibernate due to differences in the database in order to fix this a custom - * sql dialect is needed. - * Source: https://forum.hibernate.org/viewtopic.php?f=1&t=1014157 - * - * @author kevinvandevelde at atmire.com - */ -public class DSpacePostgreSQL82Dialect extends PostgreSQL82Dialect { - @Override - public void contributeTypes(final org.hibernate.boot.model.TypeContributions typeContributions, - final ServiceRegistry serviceRegistry) { - super.contributeTypes(typeContributions, serviceRegistry); - typeContributions.contributeType(new InternalPostgresUUIDType()); - } - - @Override - protected void registerHibernateType(int code, String name) { - super.registerHibernateType(code, name); - } - - protected static class InternalPostgresUUIDType extends PostgresUUIDType { - - @Override - protected boolean registerUnderJavaType() { - return true; - } - } - - /** - * Override is needed to properly support the CLOB on metadatavalue in Postgres and Oracle. - * - * @param sqlCode {@linkplain java.sql.Types JDBC type-code} for the column mapped by this type. - * @return Descriptor for the SQL/JDBC side of a value mapping. - */ - @Override - public SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { - SqlTypeDescriptor descriptor; - switch (sqlCode) { - case Types.CLOB: { - descriptor = LongVarcharTypeDescriptor.INSTANCE; - break; - } - default: { - descriptor = super.getSqlTypeDescriptorOverride(sqlCode); - break; - } - } - return descriptor; - } -} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index d5c28da096..99e49e77e4 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -80,9 +80,9 @@ db.url = jdbc:postgresql://localhost:5432/dspace db.driver = org.postgresql.Driver # Database Dialect (for Hibernate) -# * For Postgres: org.dspace.storage.rdbms.hibernate.postgres.DSpacePostgreSQL82Dialect +# * For Postgres: org.hibernate.dialect.PostgreSQL94Dialect # * For Oracle: org.hibernate.dialect.Oracle10gDialect -db.dialect = org.dspace.storage.rdbms.hibernate.postgres.DSpacePostgreSQL82Dialect +db.dialect = org.hibernate.dialect.PostgreSQL94Dialect # Database username and password db.username = dspace diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 167d2d45da..67c03808c4 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -80,9 +80,9 @@ db.url = jdbc:postgresql://localhost:5432/dspace db.driver = org.postgresql.Driver # Database Dialect (for Hibernate) -# * For Postgres: org.dspace.storage.rdbms.hibernate.postgres.DSpacePostgreSQL82Dialect +# * For Postgres: org.hibernate.dialect.PostgreSQL94Dialect # * For Oracle: org.hibernate.dialect.Oracle10gDialect -db.dialect = org.dspace.storage.rdbms.hibernate.postgres.DSpacePostgreSQL82Dialect +db.dialect = org.hibernate.dialect.PostgreSQL94Dialect # Database username and password db.username = dspace From 967e36af7a9881e6c152347e594e760807f99c3f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 2 Mar 2022 13:52:24 -0600 Subject: [PATCH 0793/1254] Fix Item & Bundle tests which check order of results. H2 2.x + Hibernate 5.x returns results ordered by UUID when unspecified. --- .../org/dspace/builder/BitstreamBuilder.java | 7 +-- .../app/rest/BitstreamControllerIT.java | 37 ++++++----- .../app/rest/BitstreamRestRepositoryIT.java | 31 ++++++--- .../app/rest/BundleRestRepositoryIT.java | 28 ++++----- .../app/rest/CollectionRestRepositoryIT.java | 8 ++- .../dspace/app/rest/ItemRestRepositoryIT.java | 58 ++++++++++------- .../app/rest/iiif/IIIFControllerIT.java | 63 +++++++++---------- 7 files changed, 131 insertions(+), 101 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index 283091778e..424833e5cc 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -18,6 +18,7 @@ import org.dspace.content.BitstreamFormat; import org.dspace.content.Bundle; import org.dspace.content.Item; import org.dspace.content.service.DSpaceObjectService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.Group; @@ -26,8 +27,6 @@ import org.dspace.eperson.Group; */ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { - public static final String ORIGINAL = "ORIGINAL"; - private Bitstream bitstream; private Item item; private Group readerGroup; @@ -158,12 +157,12 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { } private Bundle getOriginalBundle(Item item) throws SQLException, AuthorizeException { - List bundles = itemService.getBundles(item, ORIGINAL); + List bundles = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME); Bundle targetBundle = null; if (bundles.size() < 1) { // not found, create a new one - targetBundle = bundleService.create(context, item, ORIGINAL); + targetBundle = bundleService.create(context, item, Constants.CONTENT_BUNDLE_NAME); } else { // put bitstreams into first bundle targetBundle = bundles.iterator().next(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java index ca3c05ec30..4b1124071a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java @@ -16,6 +16,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import org.apache.commons.codec.CharEncoding; @@ -144,47 +146,48 @@ public class BitstreamControllerIT extends AbstractControllerIntegrationTest { String bitstreamContent = "ThisIsSomeDummyText"; - Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + List bundles = new ArrayList(); + bundles.add(BundleBuilder.createBundle(context, publicItem1) .withName("TEST FIRST BUNDLE") - .build(); + .build()); Bitstream bitstream = null; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { bitstream = BitstreamBuilder. - createBitstream(context, bundle1, is) + createBitstream(context, bundles.get(0), is) .withName("Bitstream") .withDescription("description") .withMimeType("text/plain") .build(); } - Bundle bundle2 = BundleBuilder.createBundle(context, publicItem1) + bundles.add(BundleBuilder.createBundle(context, publicItem1) .withName("TEST SECOND BUNDLE") .withBitstream(bitstream) - .build(); + .build()); context.restoreAuthSystemState(); + // While in DSpace code, Bundles are *unordered*, in Hibernate v5 + H2 v2.x, they are returned sorted by UUID. + // So, we reorder this list of created Bundles by UUID to get their expected return order. + // NOTE: Once on Hibernate v6, this might need "toString()" removed as it may sort UUIDs based on RFC 4412. + Comparator compareByUUID = Comparator.comparing(b -> b.getID().toString()); + bundles.sort(compareByUUID); + String token = getAuthToken(admin.getEmail(), password); + // Expect only the first Bundle to be returned getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle") .param("projection", "full")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - BundleMatcher.matchBundle(bundle1.getName(), - bundle1.getID(), - bundle1.getHandle(), - bundle1.getType(), - bundle1.getBitstreams()) - - ))).andExpect(jsonPath("$", Matchers.not( - BundleMatcher.matchBundle(bundle2.getName(), - bundle2.getID(), - bundle2.getHandle(), - bundle2.getType(), - bundle2.getBitstreams()) + BundleMatcher.matchBundle(bundles.get(0).getName(), + bundles.get(0).getID(), + bundles.get(0).getHandle(), + bundles.get(0).getType(), + bundles.get(0).getBitstreams()) ))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index fd95e7f584..f9c1e469fc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -20,6 +20,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.util.Comparator; +import java.util.List; import java.util.UUID; import org.apache.commons.codec.CharEncoding; @@ -45,6 +47,7 @@ import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -67,6 +70,10 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest @Autowired private GroupService groupService; + + @Autowired + private ItemService itemService; + @Test public void findAllTest() throws Exception { //We turn off the authorization system in order to create the structure as defined below @@ -1480,11 +1487,19 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .build(); } - Bundle secondBundle = BundleBuilder.createBundle(context, publicItem1) - .withName("second bundle") - .withBitstream(bitstream).build(); + // Add default content bundle to list of bundles + List bundles = itemService.getBundles(publicItem1, Constants.CONTENT_BUNDLE_NAME); - Bundle bundle = bitstream.getBundles().get(0); + // Add this bitstream to a second bundle & append to list of bundles + bundles.add(BundleBuilder.createBundle(context, publicItem1) + .withName("second bundle") + .withBitstream(bitstream).build()); + + // While in DSpace code, Bundles are *unordered*, in Hibernate v5 + H2 v2.x, they are returned sorted by UUID. + // So, we reorder this list of created Bundles by UUID to get their expected return order. + // NOTE: Once on Hibernate v6, this might need "toString()" removed as it may sort UUIDs based on RFC 4412. + Comparator compareByUUID = Comparator.comparing(b -> b.getID().toString()); + bundles.sort(compareByUUID); //Get bundle should contain the first bundle in the list getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) @@ -1492,10 +1507,10 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", BundleMatcher.matchProperties( - bundle.getName(), - bundle.getID(), - bundle.getHandle(), - bundle.getType() + bundles.get(0).getName(), + bundles.get(0).getID(), + bundles.get(0).getHandle(), + bundles.get(0).getType() ) )); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 884d551582..96385095a2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -397,26 +397,24 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { public void getBitstreamsForBundle() throws Exception { context.turnOffAuthorisationSystem(); + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .build(); + String bitstreamContent = "Dummy content"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder.createBitstream(context, item, is) + bitstream1 = BitstreamBuilder.createBitstream(context, item, is, bundle1.getName()) .withName("Bitstream") .withDescription("Description") .withMimeType("text/plain") .build(); - bitstream2 = BitstreamBuilder.createBitstream(context, item, is) + bitstream2 = BitstreamBuilder.createBitstream(context, item, is, bundle1.getName()) .withName("Bitstream2") .withDescription("Description2") .withMimeType("text/plain") .build(); } - bundle1 = BundleBuilder.createBundle(context, item) - .withName("testname") - .withBitstream(bitstream1) - .withBitstream(bitstream2) - .build(); - context.restoreAuthSystemState(); getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/bitstreams") @@ -465,26 +463,24 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { public void patchMoveBitstreams() throws Exception { context.turnOffAuthorisationSystem(); + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .build(); + String bitstreamContent = "Dummy content"; try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder.createBitstream(context, item, is) + bitstream1 = BitstreamBuilder.createBitstream(context, item, is, bundle1.getName()) .withName("Bitstream") .withDescription("Description") .withMimeType("text/plain") .build(); - bitstream2 = BitstreamBuilder.createBitstream(context, item, is) + bitstream2 = BitstreamBuilder.createBitstream(context, item, is, bundle1.getName()) .withName("Bitstream2") .withDescription("Description2") .withMimeType("text/plain") .build(); } - bundle1 = BundleBuilder.createBundle(context, item) - .withName("testname") - .withBitstream(bitstream1) - .withBitstream(bitstream2) - .build(); - context.restoreAuthSystemState(); getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/bitstreams") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 92fdecf824..6ae33cb280 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -2421,9 +2421,11 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .build(); List items = new ArrayList(); - // This comparator is used to sort our test Items by java.util.UUID (which sorts them based on the RFC - // and not based on String comparison, see also https://stackoverflow.com/a/51031298/3750035 ) - Comparator compareByUUID = Comparator.comparing(i -> i.getID()); + // Hibernate 5.x's org.hibernate.dialect.H2Dialect sorts UUIDs as if they are Strings. + // So, we must compare UUIDs as if they are strings. + // In Hibernate 6, the H2Dialect has been updated with native UUID type support, at which point + // we'd need to update the below comparator to compare them as java.util.UUID (which sorts based on RFC 4412). + Comparator compareByUUID = Comparator.comparing(i -> i.getID().toString()); Item item0 = ItemBuilder.createItem(context, collection).withTitle("Item 0").build(); items.add(item0); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 372ab084dd..95ec537727 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -123,9 +123,11 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); List items = new ArrayList(); - // This comparator is used to sort our test Items by java.util.UUID (which sorts them based on the RFC - // and not based on String comparison, see also https://stackoverflow.com/a/51031298/3750035 ) - Comparator compareByUUID = Comparator.comparing(i -> i.getID()); + // Hibernate 5.x's org.hibernate.dialect.H2Dialect sorts UUIDs as if they are Strings. + // So, we must compare UUIDs as if they are strings. + // In Hibernate 6, the H2Dialect has been updated with native UUID type support, at which point + // we'd need to update the below comparator to compare them as java.util.UUID (which sorts based on RFC 4412). + Comparator compareByUUID = Comparator.comparing(i -> i.getID().toString()); //2. Three public items that are readable by Anonymous with different subjects Item publicItem1 = ItemBuilder.createItem(context, col1) @@ -204,9 +206,11 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .build(); List items = new ArrayList(); - // This comparator is used to sort our test Items by java.util.UUID (which sorts them based on the RFC - // and not based on String comparison, see also https://stackoverflow.com/a/51031298/3750035 ) - Comparator compareByUUID = Comparator.comparing(i -> i.getID()); + // Hibernate 5.x's org.hibernate.dialect.H2Dialect sorts UUIDs as if they are Strings. + // So, we must compare UUIDs as if they are strings. + // In Hibernate 6, the H2Dialect has been updated with native UUID type support, at which point + // we'd need to update the below comparator to compare them as java.util.UUID (which sorts based on RFC 4412). + Comparator compareByUUID = Comparator.comparing(i -> i.getID().toString()); //2. Three public items that are readable by Anonymous with different subjects Item publicItem1 = ItemBuilder.createItem(context, col1) @@ -3213,16 +3217,23 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, collection).withTitle("Item").build(); - Bundle bundle0 = BundleBuilder.createBundle(context, item).withName("Bundle 0").build(); - Bundle bundle1 = BundleBuilder.createBundle(context, item).withName("Bundle 1").build(); - Bundle bundle2 = BundleBuilder.createBundle(context, item).withName("Bundle 2").build(); - Bundle bundle3 = BundleBuilder.createBundle(context, item).withName("Bundle 3").build(); - Bundle bundle4 = BundleBuilder.createBundle(context, item).withName("Bundle 4").build(); - Bundle bundle5 = BundleBuilder.createBundle(context, item).withName("Bundle 5").build(); - Bundle bundle6 = BundleBuilder.createBundle(context, item).withName("Bundle 6").build(); - Bundle bundle7 = BundleBuilder.createBundle(context, item).withName("Bundle 7").build(); - Bundle bundle8 = BundleBuilder.createBundle(context, item).withName("Bundle 8").build(); - Bundle bundle9 = BundleBuilder.createBundle(context, item).withName("Bundle 9").build(); + List bundles = new ArrayList(); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 0").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 1").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 2").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 3").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 4").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 5").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 6").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 7").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 8").build()); + bundles.add(BundleBuilder.createBundle(context, item).withName("Bundle 9").build()); + + // While in DSpace code, Bundles are *unordered*, in Hibernate v5 + H2 v2.x, they are returned sorted by UUID. + // So, we reorder this list of created Bundles by UUID to get their expected pagination ordering. + // NOTE: Once on Hibernate v6, this might need "toString()" removed as it may sort UUIDs based on RFC 4412. + Comparator compareByUUID = Comparator.comparing(b -> b.getID().toString()); + bundles.sort(compareByUUID); context.restoreAuthSystemState(); @@ -3232,11 +3243,16 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()) .andExpect(jsonPath("$", ItemMatcher.matchItemProperties(item))) .andExpect(jsonPath("$._embedded.bundles._embedded.bundles",Matchers.containsInAnyOrder( - BundleMatcher.matchProperties(bundle0.getName(), bundle0.getID(), bundle0.getHandle(), bundle0.getType()), - BundleMatcher.matchProperties(bundle1.getName(), bundle1.getID(), bundle1.getHandle(), bundle1.getType()), - BundleMatcher.matchProperties(bundle2.getName(), bundle2.getID(), bundle2.getHandle(), bundle2.getType()), - BundleMatcher.matchProperties(bundle3.getName(), bundle3.getID(), bundle3.getHandle(), bundle3.getType()), - BundleMatcher.matchProperties(bundle4.getName(), bundle4.getID(), bundle4.getHandle(), bundle4.getType()) + BundleMatcher.matchProperties(bundles.get(0).getName(), bundles.get(0).getID(), bundles.get(0).getHandle(), + bundles.get(0).getType()), + BundleMatcher.matchProperties(bundles.get(1).getName(), bundles.get(1).getID(), bundles.get(1).getHandle(), + bundles.get(1).getType()), + BundleMatcher.matchProperties(bundles.get(2).getName(), bundles.get(2).getID(), bundles.get(2).getHandle(), + bundles.get(2).getType()), + BundleMatcher.matchProperties(bundles.get(3).getName(), bundles.get(3).getID(), bundles.get(3).getHandle(), + bundles.get(3).getType()), + BundleMatcher.matchProperties(bundles.get(4).getName(), bundles.get(4).getID(), bundles.get(4).getHandle(), + bundles.get(4).getType()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/items/" + item.getID()))) .andExpect(jsonPath("$._embedded.bundles.page.size", is(5))) 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 71090cd70f..beafcb9622 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 @@ -469,40 +469,39 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest { .withMimeType("image/tiff") .build(); } - context.restoreAuthSystemState(); - // expect structures elements with label and canvas id. + + // Expected structures elements based on the above test content + // NOTE: we cannot guarantee the order of Bundles in the Manifest, therefore this test has to simply check + // that each Bundle exists in the manifest with Canvases corresponding to each bitstream. getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Global 1"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2000))) - .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(3000))) - .andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Global 2"))) - .andExpect(jsonPath("$.sequences[0].canvases[2].label", is("Global 3"))) - .andExpect(jsonPath("$.structures[0].@id", - Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0"))) - .andExpect(jsonPath("$.structures[0].label", is("Table of Contents"))) - .andExpect(jsonPath("$.structures[0].viewingHint", is("top"))) - .andExpect(jsonPath("$.structures[0].ranges[0]", - Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) - .andExpect(jsonPath("$.structures[0].ranges[1]", - Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-1"))) - .andExpect(jsonPath("$.structures[1].@id", - Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) - .andExpect(jsonPath("$.structures[1].label", is("ORIGINAL"))) - .andExpect(jsonPath("$.structures[1].canvases[0]", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) - .andExpect(jsonPath("$.structures[2].@id", - Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-1"))) - .andExpect(jsonPath("$.structures[2].label", is("IIIF"))) - .andExpect(jsonPath("$.structures[2].canvases[0]", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c1"))) - .andExpect(jsonPath("$.structures[2].canvases[1]", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c2"))) - .andExpect(jsonPath("$.service").exists()); + .andExpect(status().isOk()) + .andExpect(jsonPath("$.@context", is("http://iiif.io/api/presentation/2/context.json"))) + // should contain 3 canvases, corresponding to each bitstream + .andExpect(jsonPath("$.sequences[0].canvases[*].label", + Matchers.contains("Global 1", "Global 2", "Global 3"))) + + // First structure should be a Table of Contents + .andExpect(jsonPath("$.structures[0].@id", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0"))) + .andExpect(jsonPath("$.structures[0].label", is("Table of Contents"))) + .andExpect(jsonPath("$.structures[0].viewingHint", is("top"))) + .andExpect(jsonPath("$.structures[0].ranges[0]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-0"))) + .andExpect(jsonPath("$.structures[0].ranges[1]", + Matchers.endsWith("/iiif/" + publicItem1.getID() + "/manifest/range/r0-1"))) + + // Should contain a structure with label=IIIF, corresponding to IIIF bundle + // It should have exactly 2 canvases (corresponding to 2 bitstreams) + .andExpect(jsonPath("$.structures[?(@.label=='IIIF')].canvases[0]").exists()) + .andExpect(jsonPath("$.structures[?(@.label=='IIIF')].canvases[1]").exists()) + .andExpect(jsonPath("$.structures[?(@.label=='IIIF')].canvases[2]").doesNotExist()) + + // Should contain a structure with label=ORIGINAL, corresponding to ORIGINAL bundle + // It should have exactly 1 canvas (corresponding to 1 bitstream) + .andExpect(jsonPath("$.structures[?(@.label=='ORIGINAL')].canvases[0]").exists()) + .andExpect(jsonPath("$.structures[?(@.label=='ORIGINAL')].canvases[1]").doesNotExist()) + .andExpect(jsonPath("$.service").exists()); } @Test From b4915868ddd09e87c975445b898b6c19d4b76a3d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 7 Mar 2022 13:06:00 -0600 Subject: [PATCH 0794/1254] Entity Types now come back in a different order. Updating pagination tests based on new ordering --- .../app/rest/EntityTypeRestRepositoryIT.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java index 93aacb60e4..740a2c0dc3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -86,13 +86,13 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, - Constants.ENTITY_TYPE_NONE)), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), EntityTypeMatcher - .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")) + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalIssue")), + EntityTypeMatcher + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalVolume")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")) ))); getClient().perform(get("/api/core/entitytypes").param("size", "5").param("page", "1")) @@ -108,11 +108,12 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")), EntityTypeMatcher - .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalVolume")), + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), EntityTypeMatcher - .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalIssue")) + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, + Constants.ENTITY_TYPE_NONE)) ))); } @@ -137,10 +138,11 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .param("size", "3")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, - Constants.ENTITY_TYPE_NONE)) + "JournalIssue")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, + "JournalVolume")) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/entitytypes?"), @@ -165,7 +167,7 @@ public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")), - EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( From f8e8866f7563336dc0997dc9431b3f150b7c4e7a Mon Sep 17 00:00:00 2001 From: eskander Date: Thu, 31 Mar 2022 18:19:32 +0200 Subject: [PATCH 0795/1254] [CST-5306] Migrate Researcher Profile (REST). - added new four test functions for claim profile. - formatting the code. - removed unused classes. --- .../dspace/app/profile/ResearcherProfile.java | 16 +- .../profile/ResearcherProfileServiceImpl.java | 12 +- .../service/ResearcherProfileService.java | 8 +- .../authorize/AuthorizeServiceImpl.java | 4 - .../authorize/service/AuthorizeService.java | 1 - .../dspace/content/service/ItemService.java | 5 +- .../test/data/dspaceFolder/config/local.cfg | 4 +- .../org/dspace/app/matcher/LambdaMatcher.java | 55 ---- .../app/matcher/MetadataValueMatcher.java | 96 ------- .../java/org/dspace/builder/ItemBuilder.java | 11 +- .../impl/ResearcherProfileAutomaticClaim.java | 19 +- .../app/rest/model/ResearcherProfileRest.java | 7 +- .../ResearcherProfileRestRepository.java | 15 +- ...rProfileRestPermissionEvaluatorPlugin.java | 14 +- .../ResearcherProfileRestRepositoryIT.java | 246 ++++++++++-------- 15 files changed, 191 insertions(+), 322 deletions(-) delete mode 100644 dspace-api/src/test/java/org/dspace/app/matcher/LambdaMatcher.java delete mode 100644 dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java diff --git a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java index d282f0cb47..21dbe36da6 100644 --- a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java +++ b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfile.java @@ -7,17 +7,17 @@ */ package org.dspace.app.profile; -import org.dspace.content.Item; -import org.dspace.content.MetadataValue; -import org.dspace.util.UUIDUtils; -import org.springframework.util.Assert; +import static org.dspace.core.Constants.READ; +import static org.dspace.eperson.Group.ANONYMOUS; import java.util.Optional; import java.util.UUID; import java.util.stream.Stream; -import static org.dspace.core.Constants.READ; -import static org.dspace.eperson.Group.ANONYMOUS; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.util.UUIDUtils; +import org.springframework.util.Assert; /** * Object representing a Researcher Profile. @@ -65,7 +65,9 @@ public class ResearcherProfile { private MetadataValue getDspaceObjectOwnerMetadata(Item item) { return getMetadataValue(item, "dspace.object.owner") .filter(metadata -> UUIDUtils.fromString(metadata.getAuthority()) != null) - .orElseThrow(() -> new IllegalArgumentException("A profile item must have a valid dspace.object.owner metadata")); + .orElseThrow( + () -> new IllegalArgumentException("A profile item must have a valid dspace.object.owner metadata") + ); } private Optional getMetadataValue(Item item, String metadataField) { diff --git a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java index 1e81636ff6..75c1db45b0 100644 --- a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java @@ -15,12 +15,10 @@ import java.io.IOException; import java.net.URI; import java.sql.SQLException; import java.util.Arrays; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.UUID; -import javax.annotation.PostConstruct; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; @@ -187,8 +185,8 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { } context.turnOffAuthorisationSystem(); - itemService.addMetadata(context, item, "dspace", "object", "owner", null, ePerson.getName(), - ePerson.getID().toString(), CF_ACCEPTED); + itemService.addMetadata(context, item, "dspace", "object", "owner", null, + ePerson.getName(), ePerson.getID().toString(), CF_ACCEPTED); context.restoreAuthSystemState(); return new ResearcherProfile(item); @@ -249,8 +247,10 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, true); Item item = workspaceItem.getItem(); - itemService.addMetadata(context, item, "dc", "title", null, null, ePerson.getFullName()); - itemService.addMetadata(context, item, "dspace", "object", "owner", null, ePerson.getFullName(), id, CF_ACCEPTED); + itemService.addMetadata(context, item, "dc", "title", null, null, + ePerson.getFullName()); + itemService.addMetadata(context, item, "dspace", "object", "owner", null, + ePerson.getFullName(), id, CF_ACCEPTED); item = installItemService.installItem(context, workspaceItem); diff --git a/dspace-api/src/main/java/org/dspace/app/profile/service/ResearcherProfileService.java b/dspace-api/src/main/java/org/dspace/app/profile/service/ResearcherProfileService.java index 0d565f0754..674b440b00 100644 --- a/dspace-api/src/main/java/org/dspace/app/profile/service/ResearcherProfileService.java +++ b/dspace-api/src/main/java/org/dspace/app/profile/service/ResearcherProfileService.java @@ -7,16 +7,16 @@ */ package org.dspace.app.profile.service; +import java.net.URI; +import java.sql.SQLException; +import java.util.UUID; + import org.dspace.app.profile.ResearcherProfile; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.EPerson; -import java.net.URI; -import java.sql.SQLException; -import java.util.UUID; - /** * Service interface class for the {@link ResearcherProfile} object. The * implementation of this class is responsible for all business logic calls for 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 57300e8e18..919e82f14f 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -959,8 +959,4 @@ public class AuthorizeServiceImpl implements AuthorizeService { return query + " AND "; } } - @Override - public boolean isPartOfTheGroup(Context c, String egroup) throws SQLException { - return false; - } } 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 65bf0f31f9..9f6171a220 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 @@ -592,5 +592,4 @@ public interface AuthorizeService { */ long countAdminAuthorizedCollection(Context context, String query) throws SearchServiceException, SQLException; - public boolean isPartOfTheGroup(Context c, String egroup) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index 2d54680299..401c8fdbec 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 @@ -592,8 +592,9 @@ public interface ItemService * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - public Iterator findArchivedByMetadataField(Context context, String schema, String element, - String qualifier, String value) throws SQLException, AuthorizeException; + public Iterator findArchivedByMetadataField(Context context, String schema, + String element, String qualifier, + String value) throws SQLException, AuthorizeException; /** * Returns an iterator of in archive items possessing the passed metadata field, or only diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 11a344fa0d..34acade122 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -20,7 +20,6 @@ # For example, including "dspace.dir" in this local.cfg will override the # default value of "dspace.dir" in the dspace.cfg file. # -researcher-profile.type = Person ########################## # SERVER CONFIGURATION # ########################## @@ -144,3 +143,6 @@ authentication-ip.Student = 6.6.6.6 useProxies = true proxies.trusted.ipranges = 7.7.7.7 proxies.trusted.include_ui_ip = true + +# researcher-profile.type +researcher-profile.type = Person \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/app/matcher/LambdaMatcher.java b/dspace-api/src/test/java/org/dspace/app/matcher/LambdaMatcher.java deleted file mode 100644 index 641aee5704..0000000000 --- a/dspace-api/src/test/java/org/dspace/app/matcher/LambdaMatcher.java +++ /dev/null @@ -1,55 +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.matcher; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.Matchers; - -import java.util.function.Predicate; - -/** - * Matcher based on an {@link Predicate}. - * - * @author Luca Giamminonni (luca.giamminonni at 4science.it) - * @param the type of the instance to match - */ -public class LambdaMatcher extends BaseMatcher { - - private final Predicate matcher; - private final String description; - - public static LambdaMatcher matches(Predicate matcher) { - return new LambdaMatcher(matcher, "Matches the given predicate"); - } - - public static LambdaMatcher matches(Predicate matcher, String description) { - return new LambdaMatcher(matcher, description); - } - - public static Matcher> has(Predicate matcher) { - return Matchers.hasItem(matches(matcher)); - } - - private LambdaMatcher(Predicate matcher, String description) { - this.matcher = matcher; - this.description = description; - } - - @Override - @SuppressWarnings("unchecked") - public boolean matches(Object argument) { - return matcher.test((T) argument); - } - - @Override - public void describeTo(Description description) { - description.appendText(this.description); - } -} diff --git a/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java b/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java deleted file mode 100644 index eb2158a9ce..0000000000 --- a/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java +++ /dev/null @@ -1,96 +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.matcher; - -import org.dspace.content.MetadataValue; -import org.hamcrest.Description; -import org.hamcrest.TypeSafeMatcher; - -import java.util.Objects; - -/** - * Implementation of {@link org.hamcrest.Matcher} to match a MetadataValue by - * all its attributes. - * - * @author Luca Giamminonni (luca.giamminonni at 4science.it) - * - */ -public class MetadataValueMatcher extends TypeSafeMatcher { - - private String field; - - private String value; - - private String language; - - private String authority; - - private Integer place; - - private Integer confidence; - - private MetadataValueMatcher(String field, String value, String language, String authority, Integer place, - Integer confidence) { - - this.field = field; - this.value = value; - this.language = language; - this.authority = authority; - this.place = place; - this.confidence = confidence; - - } - - @Override - public void describeTo(Description description) { - description.appendText("MetadataValue with the following attributes [field=" + field + ", value=" - + value + ", language=" + language + ", authority=" + authority + ", place=" + place + ", confidence=" - + confidence + "]"); - } - - @Override - protected void describeMismatchSafely(MetadataValue item, Description mismatchDescription) { - mismatchDescription.appendText("was ") - .appendValue("MetadataValue [metadataField=").appendValue(item.getMetadataField().toString('.')) - .appendValue(", value=").appendValue(item.getValue()).appendValue(", language=").appendValue(language) - .appendValue(", place=").appendValue(item.getPlace()).appendValue(", authority=") - .appendValue(item.getAuthority()).appendValue(", confidence=").appendValue(item.getConfidence() + "]"); - } - - @Override - protected boolean matchesSafely(MetadataValue metadataValue) { - return Objects.equals(metadataValue.getValue(), value) && - Objects.equals(metadataValue.getMetadataField().toString('.'), field) && - Objects.equals(metadataValue.getLanguage(), language) && - Objects.equals(metadataValue.getAuthority(), authority) && - Objects.equals(metadataValue.getPlace(), place) && - Objects.equals(metadataValue.getConfidence(), confidence); - } - - public static MetadataValueMatcher with(String field, String value, String language, - String authority, Integer place, Integer confidence) { - return new MetadataValueMatcher(field, value, language, authority, place, confidence); - } - - public static MetadataValueMatcher with(String field, String value) { - return with(field, value, null, null, 0, -1); - } - - public static MetadataValueMatcher with(String field, String value, String authority, int place, int confidence) { - return with(field, value, null, authority, place, confidence); - } - - public static MetadataValueMatcher with(String field, String value, String authority, int confidence) { - return with(field, value, null, authority, 0, confidence); - } - - public static MetadataValueMatcher with(String field, String value, int place) { - return with(field, value, null, null, place, -1); - } - -} 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 ac32e9a2ae..23371df127 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -7,6 +7,9 @@ */ package org.dspace.builder; +import static org.dspace.content.MetadataSchemaEnum.DC; +import static org.dspace.content.authority.Choices.CF_ACCEPTED; + import java.io.IOException; import java.sql.SQLException; import java.util.UUID; @@ -22,9 +25,6 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import static org.dspace.content.MetadataSchemaEnum.DC; -import static org.dspace.content.authority.Choices.CF_ACCEPTED; - /** * Builder to construct Item objects * @@ -156,9 +156,6 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { return addMetadataValue(item, "dspace", "object", "owner", null, value, authority, CF_ACCEPTED); } - public ItemBuilder withDspaceObjectOwner(EPerson ePerson) { - return withDspaceObjectOwner(ePerson.getFullName(), ePerson.getID().toString()); - } public ItemBuilder makeUnDiscoverable() { item.setDiscoverable(false); return this; @@ -187,7 +184,7 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { /** * Create an admin group for the collection with the specified members * - * @param members epersons to add to the admin group + * @param ePerson epersons to add to the admin group * @return this builder * @throws SQLException * @throws AuthorizeException diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java index bcc10781e9..3c3f459fd1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/login/impl/ResearcherProfileAutomaticClaim.java @@ -7,6 +7,14 @@ */ package org.dspace.app.rest.login.impl; +import static org.apache.commons.collections4.IteratorUtils.toList; +import static org.dspace.content.authority.Choices.CF_ACCEPTED; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.profile.service.ResearcherProfileService; @@ -23,14 +31,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Assert; -import java.sql.SQLException; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -import static org.apache.commons.collections4.IteratorUtils.toList; -import static org.dspace.content.authority.Choices.CF_ACCEPTED; - /** * Implementation of {@link PostLoggedInAction} that perform an automatic claim * between the logged eperson and possible profiles without eperson present in @@ -91,7 +91,8 @@ public class ResearcherProfileAutomaticClaim implements PostLoggedInAction { Item item = findClaimableItem(context, currentUser); if (item != null) { - itemService.addMetadata(context, item, "dspace", "object", "owner", null, fullName, id.toString(), CF_ACCEPTED); + itemService.addMetadata(context, item, "dspace", "object", "owner", + null, fullName, id.toString(), CF_ACCEPTED); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java index ff789f9323..ef145d5e71 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java @@ -7,13 +7,10 @@ */ package org.dspace.app.rest.model; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import org.dspace.app.rest.RestResourceController; - -import java.util.List; import java.util.UUID; +import org.dspace.app.rest.RestResourceController; + /** * The Researcher Profile REST resource. * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java index 8be3720f52..25eb3d938a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResearcherProfileRestRepository.java @@ -7,6 +7,12 @@ */ package org.dspace.app.rest.repository; +import java.net.URI; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.collections.CollectionUtils; import org.dspace.app.profile.ResearcherProfile; import org.dspace.app.profile.service.ResearcherProfileService; @@ -25,7 +31,6 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Conditional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -34,12 +39,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import java.net.URI; -import java.sql.SQLException; -import java.util.List; -import java.util.UUID; - /** * This is the repository responsible of exposing researcher profiles. * @@ -48,7 +47,7 @@ import java.util.UUID; */ @Component(ResearcherProfileRest.CATEGORY + "." + ResearcherProfileRest.NAME) @ConditionalOnProperty( - value="researcher-profile.type" + value = "researcher-profile.type" ) public class ResearcherProfileRestRepository extends DSpaceRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java index 7545e9ad9c..66cc873db2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ResearcherProfileRestPermissionEvaluatorPlugin.java @@ -7,6 +7,14 @@ */ package org.dspace.app.rest.security; +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.util.UUID; +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.ResearcherProfileRest; import org.dspace.app.rest.utils.ContextUtil; @@ -19,12 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import java.io.Serializable; -import java.util.UUID; - -import static org.dspace.app.rest.security.DSpaceRestPermission.*; - /** * * An authenticated user is allowed to view, update or delete his or her own diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java index b316ea3acd..3d03a09912 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java @@ -7,56 +7,49 @@ */ package org.dspace.app.rest; -import com.jayway.jsonpath.JsonPath; -import org.dspace.app.rest.model.MetadataValueRest; -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.authorize.AuthorizeException; -import org.dspace.builder.*; -import org.dspace.content.Collection; -import org.dspace.content.Item; -import org.dspace.content.MetadataValue; -import org.dspace.content.service.ItemService; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; -import org.dspace.eperson.service.GroupService; -import org.dspace.services.ConfigurationService; -import org.dspace.util.UUIDUtils; -import org.junit.After; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MvcResult; - -import java.io.UnsupportedEncodingException; -import java.sql.SQLException; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Predicate; - import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static java.util.Arrays.asList; import static java.util.UUID.fromString; -import static org.dspace.app.matcher.LambdaMatcher.has; -import static org.dspace.app.matcher.MetadataValueMatcher.with; import static org.dspace.app.rest.matcher.HalMatcher.matchLinks; -import static org.dspace.app.rest.matcher.MetadataMatcher.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +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.MetadataMatcher.matchMetadataNotEmpty; +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.*; import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +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.io.UnsupportedEncodingException; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import com.jayway.jsonpath.JsonPath; +import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.patch.AddOperation; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.dspace.app.rest.repository.ResearcherProfileRestRepository; +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.Item; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + /** * Integration tests for {@link ResearcherProfileRestRepository}. * @@ -68,20 +61,12 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra @Autowired private ConfigurationService configurationService; - @Autowired - private ItemService itemService; - - @Autowired - private GroupService groupService; - private EPerson user; private EPerson anotherUser; private Collection personCollection; - private Group administrators; - /** * Tests setup. */ @@ -112,8 +97,6 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra .withTemplateItem() .build(); - administrators = groupService.findByName(context, Group.ADMIN); - configurationService.setProperty("researcher-profile.collection.uuid", personCollection.getID().toString()); configurationService.setProperty("claimable.entityType", "Person"); @@ -254,7 +237,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra getClient(authToken).perform(post("/api/eperson/profiles/") .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.id", is(id))) .andExpect(jsonPath("$.visible", is(false))) .andExpect(jsonPath("$.type", is("profile"))) .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson"))); @@ -265,7 +248,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("item"))) - .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id.toString(), 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id, 0))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) @@ -294,7 +277,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra .param("eperson", id) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.id", is(id))) .andExpect(jsonPath("$.visible", is(false))) .andExpect(jsonPath("$.type", is("profile"))) .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson"))); @@ -305,7 +288,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra getClient(authToken).perform(get("/api/eperson/profiles/{id}/item", id)) .andExpect(status().isOk()) .andExpect(jsonPath("$.type", is("item"))) - .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id.toString(), 0))) + .andExpect(jsonPath("$.metadata", matchMetadata("dspace.object.owner", name, id, 0))) .andExpect(jsonPath("$.metadata", matchMetadata("dspace.entity.type", "Person", 0))); getClient(authToken).perform(get("/api/eperson/profiles/{id}/eperson", id)) @@ -317,7 +300,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.id", is(id))) .andExpect(jsonPath("$.visible", is(false))) .andExpect(jsonPath("$.type", is("profile"))) .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + id, "item", "eperson"))); @@ -356,14 +339,14 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra getClient(authToken).perform(post("/api/eperson/profiles/") .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.id", is(id))) .andExpect(jsonPath("$.visible", is(false))) .andExpect(jsonPath("$.type", is("profile"))); getClient(authToken).perform(post("/api/eperson/profiles/") .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isConflict()) - .andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(jsonPath("$.id", is(id))) .andExpect(jsonPath("$.visible", is(false))) .andExpect(jsonPath("$.type", is("profile"))); @@ -399,7 +382,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra String id = user.getID().toString(); String authToken = getAuthToken(user.getEmail(), password); - AtomicReference itemIdRef = new AtomicReference(); + AtomicReference itemIdRef = new AtomicReference<>(); getClient(authToken).perform(post("/api/eperson/profiles/") .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -437,7 +420,7 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra String id = user.getID().toString(); String authToken = getAuthToken(user.getEmail(), password); - AtomicReference itemIdRef = new AtomicReference(); + AtomicReference itemIdRef = new AtomicReference<>(); getClient(authToken).perform(post("/api/eperson/profiles/") .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -710,6 +693,28 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra .andExpect(jsonPath("$.visible", is(true))); } + @Test + public void testPatchToChangeVisibleAttributeOfNotExistProfile() throws Exception { + + String id = user.getID().toString(); + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.visible", is(false))); + + getClient(authToken).perform(delete("/api/eperson/profiles/{id}", id)) + .andExpect(status().isNoContent()); + + List operations = asList(new ReplaceOperation("/visible", true)); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", id) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + /** * Verify that after an user login an automatic claim between the logged eperson * and possible profiles without eperson is done. @@ -866,11 +871,11 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra context.turnOffAuthorisationSystem(); final Item person = ItemBuilder.createItem(context, personCollection) - .withTitle("dc.title") + .withTitle("Test User 1") .build(); final Item otherPerson = ItemBuilder.createItem(context, personCollection) - .withTitle("dc.title") + .withTitle("Test User 2") .build(); context.restoreAuthSystemState(); @@ -883,8 +888,8 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra .andExpect(status().isCreated()) .andExpect(jsonPath("$.id", is(id))) .andExpect(jsonPath("$.type", is("profile"))) - .andExpect(jsonPath("$", - matchLinks("http://localhost/api/eperson/profiles/" + user.getID(), "item", "eperson"))); + .andExpect(jsonPath("$", matchLinks("http://localhost/api/eperson/profiles/" + user.getID(), + "item", "eperson"))); getClient(authToken).perform(get("/api/eperson/profiles/{id}", id)) .andExpect(status().isOk()); @@ -929,12 +934,73 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra } @Test - public void claimForNotAllowedEntityType() throws Exception { - String id = user.getID().toString(); - String name = user.getName(); + public void testNotAdminUserClaimProfileOfAnotherUser() throws Exception { context.turnOffAuthorisationSystem(); + final Item person = ItemBuilder.createItem(context, personCollection) + .withTitle("Test User 1") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(user.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .param("eperson" , anotherUser.getID().toString()) + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/items/" + person.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void testAdminUserClaimProfileOfNotExistingPersonId() throws Exception { + + String id = "bef23ba3-9aeb-4f7b-b153-77b0f1fc3612"; + + context.turnOffAuthorisationSystem(); + + final Item person = ItemBuilder.createItem(context, personCollection) + .withTitle("Test User 1") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .param("eperson" , id) + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/items/" + person.getID().toString())) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void testAdminUserClaimProfileOfWrongPersonId() throws Exception { + + String id = "invalid_id"; + + context.turnOffAuthorisationSystem(); + + final Item person = ItemBuilder.createItem(context, personCollection) + .withTitle("Test User 1") + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .param("eperson" , id) + .contentType(TEXT_URI_LIST) + .content("http://localhost:8080/server/api/core/items/" + person.getID().toString())) + .andExpect(status().isBadRequest()); + } + + @Test + public void claimForNotAllowedEntityType() throws Exception { + context.turnOffAuthorisationSystem(); + final Collection publications = CollectionBuilder.createCollection(context, parentCommunity) .withEntityType("Publication") .build(); @@ -953,30 +1019,6 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra .andExpect(status().isBadRequest()); } - @Test - public void testCloneFromExternalSourceRecordNotFound() throws Exception { - - String authToken = getAuthToken(user.getEmail(), password); - - getClient(authToken) - .perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/integration/externalsources/orcid/entryValues/FAKE")) - .andExpect(status().isBadRequest()); - } - - @Test - public void testCloneFromExternalSourceMultipleUri() throws Exception { - - String authToken = getAuthToken(user.getEmail(), password); - - getClient(authToken) - .perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/integration/externalsources/orcid/entryValues/id \n " - + "http://localhost:8080/server/api/integration/externalsources/dspace/entryValues/id")) - .andExpect(status().isBadRequest()); - - } - @Test public void testCloneFromExternalProfileAlreadyAssociated() throws Exception { @@ -984,34 +1026,16 @@ public class ResearcherProfileRestRepositoryIT extends AbstractControllerIntegra String authToken = getAuthToken(user.getEmail(), password); getClient(authToken).perform(post("/api/eperson/profiles/").contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isCreated()).andExpect(jsonPath("$.id", is(id.toString()))) + .andExpect(status().isCreated()).andExpect(jsonPath("$.id", is(id))) .andExpect(jsonPath("$.visible", is(false))).andExpect(jsonPath("$.type", is("profile"))); getClient(authToken) .perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/integration/externalsources/orcid/entryValues/id")) + .content("http://localhost:8080/server/api/core/items/" + id)) .andExpect(status().isConflict()); } - @Test - public void testCloneFromExternalCollectionNotSet() throws Exception { - - configurationService.setProperty("researcher-profile.collection.uuid", "not-existing"); - String id = user.getID().toString(); - String authToken = getAuthToken(user.getEmail(), password); - - getClient(authToken).perform(post("/api/eperson/profiles/").contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isCreated()).andExpect(jsonPath("$.id", is(id.toString()))) - .andExpect(jsonPath("$.visible", is(false))).andExpect(jsonPath("$.type", is("profile"))); - - getClient(authToken) - .perform(post("/api/eperson/profiles/").contentType(TEXT_URI_LIST) - .content("http://localhost:8080/server/api/integration/externalsources/orcid/entryValues/id \n " - + "http://localhost:8080/server/api/integration/externalsources/dspace/entryValues/id")) - .andExpect(status().isBadRequest()); - } - - private String getItemIdByProfileId(String token, String id) throws SQLException, Exception { + private String getItemIdByProfileId(String token, String id) throws Exception { MvcResult result = getClient(token).perform(get("/api/eperson/profiles/{id}/item", id)) .andExpect(status().isOk()) .andReturn(); From 1050d02a97cbe0ec83336f8bb1c8906f88aad62c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 2 Apr 2022 13:11:11 +0200 Subject: [PATCH 0796/1254] refactored live import client and some utils class --- .../scopus/service/LiveImportClient.java | 3 +- .../scopus/service/LiveImportClientImpl.java | 56 ++++++++----- .../importer/external/service/DoiCheck.java | 52 ++++++++++++ .../config/spring/api/external-services.xml | 2 + .../AbstractLiveImportIntegrationTest.java | 80 +++++++++++++++++++ 5 files changed, 172 insertions(+), 21 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java index 50006fd486..bbc69c2a3d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java @@ -7,7 +7,6 @@ */ package org.dspace.importer.external.scopus.service; -import java.io.InputStream; import java.util.Map; /** @@ -17,6 +16,6 @@ import java.util.Map; */ public interface LiveImportClient { - public InputStream executeHttpGetRequest(int timeout, String URL, Map requestParams); + public String executeHttpGetRequest(int timeout, String URL, Map requestParams); } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java index f11e2fc4f2..d5503831d3 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java @@ -9,20 +9,21 @@ package org.dspace.importer.external.scopus.service; import java.io.InputStream; import java.net.URISyntaxException; +import java.nio.charset.Charset; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.DefaultProxyRoutePlanner; import org.apache.log4j.Logger; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -36,34 +37,32 @@ public class LiveImportClientImpl implements LiveImportClient { private static final Logger log = Logger.getLogger(LiveImportClientImpl.class); + private CloseableHttpClient httpClient; + @Autowired private ConfigurationService configurationService; @Override - public InputStream executeHttpGetRequest(int timeout, String URL, Map requestParams) { + public String executeHttpGetRequest(int timeout, String URL, Map requestParams) { HttpGet method = null; - String proxyHost = configurationService.getProperty("http.proxy.host"); - String proxyPort = configurationService.getProperty("http.proxy.port"); - try { - HttpClientBuilder hcBuilder = HttpClients.custom(); + try (CloseableHttpClient httpClient = Optional.ofNullable(this.httpClient) + .orElseGet(HttpClients::createDefault)) { + Builder requestConfigBuilder = RequestConfig.custom(); requestConfigBuilder.setConnectionRequestTimeout(timeout); - - if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) { - HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http"); - DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); - hcBuilder.setRoutePlanner(routePlanner); - } + RequestConfig defaultRequestConfig = requestConfigBuilder.build(); method = new HttpGet(getSearchUrl(URL, requestParams)); - method.setConfig(requestConfigBuilder.build()); + method.setConfig(defaultRequestConfig); - HttpClient client = hcBuilder.build(); - HttpResponse httpResponse = client.execute(method); + configureProxy(method, defaultRequestConfig); + + HttpResponse httpResponse = httpClient.execute(method); if (isNotSuccessfull(httpResponse)) { throw new RuntimeException(); } - return httpResponse.getEntity().getContent(); + InputStream inputStream = httpResponse.getEntity().getContent(); + return IOUtils.toString(inputStream, Charset.defaultCharset()); } catch (Exception e1) { log.error(e1.getMessage(), e1); } finally { @@ -71,7 +70,18 @@ public class LiveImportClientImpl implements LiveImportClient { method.releaseConnection(); } } - return null; + return StringUtils.EMPTY; + } + + private void configureProxy(HttpGet method, RequestConfig defaultRequestConfig) { + String proxyHost = configurationService.getProperty("http.proxy.host"); + String proxyPort = configurationService.getProperty("http.proxy.port"); + if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) { + RequestConfig requestConfig = RequestConfig.copy(defaultRequestConfig) + .setProxy(new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http")) + .build(); + method.setConfig(requestConfig); + } } private String getSearchUrl(String URL, Map requestParams) throws URISyntaxException { @@ -91,4 +101,12 @@ public class LiveImportClientImpl implements LiveImportClient { return response.getStatusLine().getStatusCode(); } + public CloseableHttpClient getHttpClient() { + return httpClient; + } + + public void setHttpClient(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } + } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java b/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java new file mode 100644 index 0000000000..1285d6ea32 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.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.importer.external.service; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Utility class that provides methods to check if a given string is a DOI and exists on CrossRef services + * + * @author Corrado Lombardi (corrado.lombardi at 4science.it) + */ +public class DoiCheck { + + private static final List DOI_PREFIXES = Arrays.asList("http://dx.doi.org/", "https://dx.doi.org/"); + + private static final Pattern PATTERN = Pattern.compile("10.\\d{4,9}/[-._;()/:A-Z0-9]+" + + "|10.1002/[^\\s]+" + + "|10.\\d{4}/\\d+-\\d+X?(\\d+)" + + "\\d+<[\\d\\w]+:[\\d\\w]*>\\d+.\\d+.\\w+;\\d" + + "|10.1021/\\w\\w\\d++" + + "|10.1207/[\\w\\d]+\\&\\d+_\\d+", + Pattern.CASE_INSENSITIVE); + + + private DoiCheck() { + } + + public static boolean isDoi(final String value) { + + Matcher m = PATTERN.matcher(purgeDoiValue(value)); + return m.matches(); + } + + + public static String purgeDoiValue(final String query) { + String value = query.replaceAll(",", ""); + for (final String prefix : DOI_PREFIXES) { + value = value.replaceAll(prefix, ""); + } + return value.trim(); + } + +} 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 ac163d3581..2157556d46 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 @@ -6,6 +6,8 @@ + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java new file mode 100644 index 0000000000..ca3195b344 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.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; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.UnsupportedEncodingException; +import java.util.Collection; + +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.tools.ant.filters.StringInputStream; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class AbstractLiveImportIntegrationTest extends AbstractControllerIntegrationTest { + + protected boolean matchRecords(Collection recordsImported, Collection records2match) { + ImportRecord firstImported = recordsImported.iterator().next(); + ImportRecord secondImported = recordsImported.iterator().next(); + ImportRecord first2match = recordsImported.iterator().next(); + ImportRecord second2match = recordsImported.iterator().next(); + boolean checkFirstRecord = firstImported.getValueList().containsAll(first2match.getValueList()); + boolean checkSecondRecord = secondImported.getValueList().containsAll(second2match.getValueList()); + return checkFirstRecord && checkSecondRecord; + } + + protected MetadatumDTO createMetadatumDTO(String schema, String element, String qualifier, String value) { + MetadatumDTO metadatumDTO = new MetadatumDTO(); + metadatumDTO.setSchema(schema); + metadatumDTO.setElement(element); + metadatumDTO.setQualifier(qualifier); + metadatumDTO.setValue(value); + return metadatumDTO; + } + + protected CloseableHttpResponse mockResponse(String xmlExample, int statusCode, String reason) + throws UnsupportedEncodingException { + BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); + basicHttpEntity.setChunked(true); + basicHttpEntity.setContent(new StringInputStream(xmlExample)); + + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(statusLine(statusCode, reason)); + when(response.getEntity()).thenReturn(basicHttpEntity); + return response; + } + + protected StatusLine statusLine(int statusCode, String reason) { + return new StatusLine() { + @Override + public ProtocolVersion getProtocolVersion() { + return new ProtocolVersion("http", 1, 1); + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getReasonPhrase() { + return reason; + } + }; + } + +} \ No newline at end of file From 5d0bf51d5a6c33c3ed39a64b4d5064a7c506de86 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 2 Apr 2022 13:12:25 +0200 Subject: [PATCH 0797/1254] [CST-5303] added tests for CrossRef live import service --- .../assetstore/crossRef-test.json | 309 ++++++++++++++++++ ...CrossRefImportMetadataSourceServiceIT.java | 146 +++++++++ .../src/test/resources/test-config.properties | 2 + 3 files changed, 457 insertions(+) create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/crossRef-test.json create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/crossRef-test.json b/dspace-api/src/test/data/dspaceFolder/assetstore/crossRef-test.json new file mode 100644 index 0000000000..69a9433868 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/crossRef-test.json @@ -0,0 +1,309 @@ +{ + "status": "ok", + "message-type": "work-list", + "message-version": "1.0.0", + "message": { + "facets": {}, + "total-results": 10, + "items": [ + { + "indexed": { + "date-parts": [ + [ + 2021, + 12, + 22 + ] + ], + "date-time": "2021-12-22T10:58:16Z", + "timestamp": 1640170696598 + }, + "reference-count": 0, + "publisher": "Petro Mohyla Black Sea National University", + "issue": "2", + "content-domain": { + "domain": [], + "crossmark-restriction": false + }, + "short-container-title": [ + "Ukr. ž. med. bìol. sportu" + ], + "published-print": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + }, + "DOI": "10.26693/jmbs01.02.184", + "type": "journal-article", + "created": { + "date-parts": [ + [ + 2017, + 9, + 7 + ] + ], + "date-time": "2017-09-07T13:30:46Z", + "timestamp": 1504791046000 + }, + "page": "184-187", + "source": "Crossref", + "is-referenced-by-count": 0, + "title": [ + "State of Awareness of Freshers’ Groups Chortkiv State Medical College of Prevention of Iodine Deficiency Diseases" + ], + "prefix": "10.26693", + "volume": "1", + "author": [ + { + "given": "L.V.", + "family": "Senyuk", + "sequence": "first", + "affiliation": [] + }, + { + "name": "Chortkiv State Medical College 7, Gogola St., Chortkiv, Ternopil region 48500, Ukraine", + "sequence": "first", + "affiliation": [] + } + ], + "member": "11225", + "published-online": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + }, + "container-title": [ + "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu" + ], + "original-title": [ + "СТАН ОБІЗНАНОСТІ СТУДЕНТІВ НОВОНАБРАНИХ ГРУП ЧОРТКІВСЬКОГО ДЕРЖАВНОГО МЕДИЧНОГО КОЛЕДЖУ З ПИТАНЬ ПРОФІЛАКТИКИ ЙОДОДЕФІЦИТНИХ ЗАХВОРЮВАНЬ" + ], + "deposited": { + "date-parts": [ + [ + 2017, + 9, + 8 + ] + ], + "date-time": "2017-09-08T10:14:53Z", + "timestamp": 1504865693000 + }, + "score": 22.728952, + "issued": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + }, + "references-count": 0, + "journal-issue": { + "issue": "2", + "published-online": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + }, + "published-print": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + } + }, + "URL": "http://dx.doi.org/10.26693/jmbs01.02.184", + "ISSN": [ + "2415-3060" + ], + "issn-type": [ + { + "value": "2415-3060", + "type": "print" + } + ], + "published": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + } + }, + { + "indexed": { + "date-parts": [ + [ + 2022, + 3, + 29 + ] + ], + "date-time": "2022-03-29T13:04:48Z", + "timestamp": 1648559088439 + }, + "reference-count": 0, + "publisher": "Petro Mohyla Black Sea National University", + "issue": "2", + "content-domain": { + "domain": [], + "crossmark-restriction": false + }, + "short-container-title": [ + "Ukr. ž. med. bìol. sportu" + ], + "published-print": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + }, + "DOI": "10.26693/jmbs01.02.105", + "type": "journal-article", + "created": { + "date-parts": [ + [ + 2017, + 9, + 1 + ] + ], + "date-time": "2017-09-01T10:04:04Z", + "timestamp": 1504260244000 + }, + "page": "105-108", + "source": "Crossref", + "is-referenced-by-count": 0, + "title": [ + "Ischemic Heart Disease and Role of Nurse of Cardiology Department" + ], + "prefix": "10.26693", + "volume": "1", + "author": [ + { + "given": "K. І.", + "family": "Kozak", + "sequence": "first", + "affiliation": [] + }, + { + "name": "Chortkiv State Medical College 7, Gogola St., Chortkiv, Ternopil region 48500, Ukraine", + "sequence": "first", + "affiliation": [] + } + ], + "member": "11225", + "published-online": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + }, + "container-title": [ + "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu" + ], + "original-title": [ + "ІШЕМІЧНА ХВОРОБА СЕРЦЯ ТА РОЛЬ МЕДИЧНОЇ СЕСТРИ КАРДІОЛОГІЧНОГО ВІДДІЛЕННЯ" + ], + "deposited": { + "date-parts": [ + [ + 2017, + 9, + 2 + ] + ], + "date-time": "2017-09-02T12:36:15Z", + "timestamp": 1504355775000 + }, + "score": 18.263277, + "resource": { + "primary": { + "URL": "http://en.jmbs.com.ua/archive/1/2/105" + } + }, + "issued": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + }, + "references-count": 0, + "journal-issue": { + "issue": "2", + "published-online": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + }, + "published-print": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + } + }, + "URL": "http://dx.doi.org/10.26693/jmbs01.02.105", + "ISSN": [ + "2415-3060" + ], + "issn-type": [ + { + "value": "2415-3060", + "type": "print" + } + ], + "published": { + "date-parts": [ + [ + 2016, + 5, + 19 + ] + ] + } + } + ], + "items-per-page": 2, + "query": { + "start-index": 0, + "search-terms": "chortkiv" + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..ef7ad6e862 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.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.app.rest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.dspace.importer.external.crossref.CrossRefImportMetadataSourceServiceImpl; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.scopus.service.LiveImportClientImpl; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link CrossRefImportMetadataSourceServiceImpl} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class CrossRefImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Autowired + private CrossRefImportMetadataSourceServiceImpl crossRefServiceImpl; + + @Test + public void crossRefImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.crossRef").toString(); + try (FileInputStream crossRefResp = new FileInputStream(path)) { + + String crossRefRespXmlResp = IOUtils.toString(crossRefResp, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(crossRefRespXmlResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection collection2match = getRecords(); + Collection recordsImported = crossRefServiceImpl.getRecords("test query", 0, 2); + assertEquals(2, recordsImported.size()); + assertTrue(matchRecords(recordsImported, collection2match)); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + @Test + public void crossRefImportMetadataGetRecordsCountTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.crossRef").toString(); + try (FileInputStream file = new FileInputStream(path)) { + String crossRefRespXmlResp = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(crossRefRespXmlResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + int tot = crossRefServiceImpl.getRecordsCount("test query"); + assertEquals(10, tot); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + private Collection getRecords() { + Collection records = new LinkedList(); + //define first record + List metadatums = new ArrayList(); + MetadatumDTO title = createMetadatumDTO("dc", "title", null, + "State of Awareness of Freshers’ Groups Chortkiv State" + + " Medical College of Prevention of Iodine Deficiency Diseases"); + MetadatumDTO type = createMetadatumDTO("dc", "type", null, "journal-article"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2016"); + 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", "relation", "issn", "2415-3060"); + MetadatumDTO volume = createMetadatumDTO("oaire", "citation", "volume", "1"); + MetadatumDTO issue = createMetadatumDTO("oaire", "citation", "issue", "2"); + + metadatums.add(title); + metadatums.add(type); + metadatums.add(date); + metadatums.add(ispartof); + metadatums.add(doi); + metadatums.add(issn); + metadatums.add(volume); + metadatums.add(issue); + + ImportRecord firstrRecord = new ImportRecord(metadatums); + + //define second record + List metadatums2 = new ArrayList(); + MetadatumDTO title2 = createMetadatumDTO("dc", "title", null, + "Ischemic Heart Disease and Role of Nurse of Cardiology Department"); + MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "journal-article"); + MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2016"); + 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", "relation", "issn", "2415-3060"); + MetadatumDTO volume2 = createMetadatumDTO("oaire", "citation", "volume", "1"); + MetadatumDTO issue2 = createMetadatumDTO("oaire", "citation", "issue", "2"); + + metadatums2.add(title2); + metadatums2.add(type2); + metadatums2.add(date2); + metadatums2.add(ispartof2); + metadatums2.add(doi2); + metadatums2.add(issn2); + metadatums2.add(volume2); + metadatums2.add(issue2); + + ImportRecord secondRecord = new ImportRecord(metadatums2); + records.add(firstrRecord); + records.add(secondRecord); + return records; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/test-config.properties b/dspace-server-webapp/src/test/resources/test-config.properties index 3af96b20fc..3942eb9b1c 100644 --- a/dspace-server-webapp/src/test/resources/test-config.properties +++ b/dspace-server-webapp/src/test/resources/test-config.properties @@ -14,3 +14,5 @@ test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf #Path for a test Taskfile for the curate script test.curateTaskFile = ./target/testing/dspace/assetstore/curate.txt + +test.crossRef = ./target/testing/dspace/assetstore/crossRef-test.json From 181bdd04d1145cd4a99528b9bd136c0da18e0065 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Sat, 2 Apr 2022 13:13:38 +0200 Subject: [PATCH 0798/1254] [CST-5303] porting of CrossRef live import service --- dspace-api/pom.xml | 5 + .../CrossRefAuthorMetadataProcessor.java | 44 +++ .../crossref/CrossRefFieldMapping.java | 37 ++ ...ossRefImportMetadataSourceServiceImpl.java | 332 ++++++++++++++++++ .../JsonPathMetadataProcessor.java | 16 + .../SimpleJsonPathMetadataContributor.java | 138 ++++++++ .../spring-dspace-addon-import-services.xml | 5 + .../spring/api/crossref-integration.xml | 144 ++++++++ .../config/spring/api/external-services.xml | 12 +- 9 files changed, 732 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/JsonPathMetadataProcessor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java create mode 100644 dspace/config/spring/api/crossref-integration.xml diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 66ff4e69bc..ba816fca38 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -337,6 +337,11 @@ + + net.minidev + json-smart + 2.3 + org.apache.logging.log4j log4j-api diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java new file mode 100644 index 0000000000..c337eebb03 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefAuthorMetadataProcessor.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.crossref; + +import java.util.ArrayList; +import java.util.Collection; + +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.ReadContext; +import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor; +import org.json.JSONArray; + +public class CrossRefAuthorMetadataProcessor implements JsonPathMetadataProcessor { + + private String givenNameJsonPath; + private String familyNameJsonPath; + + public void setGivenNameJsonPath(String givenNameJsonPath) { + this.givenNameJsonPath = givenNameJsonPath; + } + + public void setFamilyNameJsonPath(String familyNameJsonPath) { + this.familyNameJsonPath = familyNameJsonPath; + } + + @Override + public Collection processMetadata(String json) { + ReadContext ctx = JsonPath.parse(json); + JSONArray givenNames = ctx.read(givenNameJsonPath); + JSONArray familyNames = ctx.read(familyNameJsonPath); + Collection values = new ArrayList<>(); + for (int i = 0; i < givenNames.length(); i++) { + String name = givenNames.get(i).toString(); + values.add(name + " " + familyNames.get(i).toString()); + } + return values; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefFieldMapping.java new file mode 100644 index 0000000000..4bac11ab34 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefFieldMapping.java @@ -0,0 +1,37 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.crossref; + +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 ArXiv metadatum fields on the DSpace metadatum fields + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ +public class CrossRefFieldMapping 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 = "crossrefMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..7c7644b3ef --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -0,0 +1,332 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.crossref; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; +import javax.el.MethodNotFoundException; + +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.utils.URIBuilder; +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.scopus.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 CrossRef + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + + private static final String ENDPOINT_WORKS = "https://api.crossref.org/works"; + + @Autowired + private LiveImportClient liveImportClient; + + @Override + public String getImportSource() { + return "crossref"; + } + + @Override + public void init() throws Exception {} + + @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 CrossRef"); + } + + public String getID(String query) { + if (DoiCheck.isDoi(query)) { + return "filter=doi:" + 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<>(); + Integer count = query.getParameterAsClass("count", Integer.class); + Integer start = query.getParameterAsClass("start", Integer.class); + + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_WORKS); + uriBuilder.addParameter("query", query.getParameterAsClass("query", String.class)); + if (Objects.nonNull(count)) { + uriBuilder.addParameter("rows", count.toString()); + } + if (Objects.nonNull(start)) { + uriBuilder.addParameter("offset", start.toString()); + } + + String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + ReadContext ctx = JsonPath.parse(response); + Object o = ctx.read("$.message.items"); + 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; + } + + } + + 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<>(); + URIBuilder uriBuilder = new URIBuilder( + ENDPOINT_WORKS + "/" + query.getParameterAsClass("id", String.class)); + String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + ReadContext ctx = JsonPath.parse(responseString); + Object o = ctx.read("$.message"); + 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; + } + } + + 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); + String author = query.getParameterAsClass("author", String.class); + String title = query.getParameterAsClass("title", String.class); + String bibliographics = query.getParameterAsClass("bibliographics", String.class); + List results = new ArrayList<>(); + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_WORKS); + if (Objects.nonNull(queryValue)) { + uriBuilder.addParameter("query", queryValue); + } + if (Objects.nonNull(count)) { + uriBuilder.addParameter("rows", count.toString()); + } + if (Objects.nonNull(start)) { + uriBuilder.addParameter("offset", start.toString()); + } + if (Objects.nonNull(author)) { + uriBuilder.addParameter("query.author", author); + } + if (Objects.nonNull(title )) { + uriBuilder.addParameter("query.container-title", title); + } + if (Objects.nonNull(bibliographics)) { + uriBuilder.addParameter("query.bibliographic", bibliographics); + } + + String resp = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + ReadContext ctx = JsonPath.parse(resp); + Object o = ctx.read("$.message.items[*]"); + 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; + } + + } + + 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 { + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_WORKS); + uriBuilder.addParameter("query", query.getParameterAsClass("query", String.class)); + String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + ReadContext ctx = JsonPath.parse(responseString); + return ctx.read("$.message.total-results"); + } + } + + 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 { + URIBuilder uriBuilder = new URIBuilder( + ENDPOINT_WORKS + "/" + query.getParameterAsClass("id", String.class)); + String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + ReadContext ctx = JsonPath.parse(responseString); + return StringUtils.equals(ctx.read("$.status"), "ok") ? 1 : 0; + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/JsonPathMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/JsonPathMetadataProcessor.java new file mode 100644 index 0000000000..961f4a6066 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/JsonPathMetadataProcessor.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.importer.external.metadatamapping.contributor; + +import java.util.Collection; + +public interface JsonPathMetadataProcessor { + + public Collection processMetadata(String json); + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..6148b3a115 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java @@ -0,0 +1,138 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.ReadContext; +import net.minidev.json.JSONArray; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; + +public class SimpleJsonPathMetadataContributor implements MetadataContributor { + + private String query; + + private MetadataFieldConfig field; + + protected JsonPathMetadataProcessor metadataProcessor; + + /** + * Initialize SimpleJsonPathMetadataContributor with a query, prefixToNamespaceMapping and MetadataFieldConfig + * + * @param query The JSonPath query + * @param field the matadata field to map the result of the Json path query + * MetadataFieldConfig + */ + public SimpleJsonPathMetadataContributor(String query, MetadataFieldConfig field) { + this.query = query; + this.field = field; + } + + + /** + * Unused by this implementation + */ + @Override + public void setMetadataFieldMapping(MetadataFieldMapping> rt) { + + } + + /** + * Empty constructor for SimpleJsonPathMetadataContributor + */ + public SimpleJsonPathMetadataContributor() { + + } + + /** + * Return the MetadataFieldConfig used while retrieving MetadatumDTO + * + * @return MetadataFieldConfig + */ + public MetadataFieldConfig getField() { + return field; + } + + /** + * Setting the MetadataFieldConfig + * + * @param field MetadataFieldConfig used while retrieving MetadatumDTO + */ + public void setField(MetadataFieldConfig field) { + this.field = field; + } + + /** + * Return query used to create the JSonPath + * + * @return the query this instance is based on + */ + public String getQuery() { + return query; + } + + /** + * Return query used to create the JSonPath + * + */ + public void setQuery(String query) { + this.query = query; + } + + /** + * Used to process data got by jsonpath expression, like arrays to stringify, change date format or else + * If it is null, toString will be used. + * + * @param metadataProcessor + */ + public void setMetadataProcessor(JsonPathMetadataProcessor metadataProcessor) { + this.metadataProcessor = 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. + * @return a collection of import records. Only the identifier of the found records may be put in the record. + */ + @Override + public Collection contributeMetadata(String fullJson) { + Collection metadata = new ArrayList<>(); + Collection metadataValue = new ArrayList<>(); + if (metadataProcessor != null) { + metadataValue = metadataProcessor.processMetadata(fullJson); + } else { + ReadContext ctx = JsonPath.parse(fullJson); + Object o = ctx.read(query); + if (o.getClass().isAssignableFrom(JSONArray.class)) { + JSONArray results = (JSONArray)o; + for (int i = 0; i < results.size(); i++) { + String value = results.get(i).toString(); + metadataValue.add(value); + } + } else { + metadataValue.add(o.toString()); + } + } + for (String value : metadataValue) { + MetadatumDTO metadatumDto = new MetadatumDTO(); + metadatumDto.setValue(value); + metadatumDto.setElement(field.getElement()); + metadatumDto.setQualifier(field.getQualifier()); + metadatumDto.setSchema(field.getSchema()); + metadata.add(metadatumDto); + } + return metadata; + } + +} \ No newline at end of file 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 5e69ee9c42..96149c581c 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 @@ -116,6 +116,11 @@ + + + + + diff --git a/dspace/config/spring/api/crossref-integration.xml b/dspace/config/spring/api/crossref-integration.xml new file mode 100644 index 0000000000..1d921d9341 --- /dev/null +++ b/dspace/config/spring/api/crossref-integration.xml @@ -0,0 +1,144 @@ + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 7f1295f839..90da4ed0c3 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -94,5 +94,15 @@ - + + + + + + + Publication + + + + \ No newline at end of file From cb44a8cf15c256dffcf94acfea190eefc212d422 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 4 Apr 2022 09:56:15 +0200 Subject: [PATCH 0799/1254] [CST-5303] porting of VuFind live import service --- ...VuFindImportMetadataSourceServiceImpl.java | 289 ++++++++++++++++++ .../metadatamapping/VuFindFieldMapping.java | 32 ++ .../spring-dspace-addon-import-services.xml | 8 +- .../config/spring/api/external-services.xml | 11 + .../config/spring/api/vufind-integration.xml | 155 ++++++++++ 5 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/vufind/metadatamapping/VuFindFieldMapping.java create mode 100644 dspace/config/spring/api/vufind-integration.xml 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 new file mode 100644 index 0000000000..3293a0b886 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java @@ -0,0 +1,289 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.vufind; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.Callable; +import javax.el.MethodNotFoundException; + +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.utils.URIBuilder; +import org.apache.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.scopus.service.LiveImportClient; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.components.QuerySource; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements a data source for querying VuFind + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class VuFindImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + + private static final Logger log = Logger.getLogger(VuFindImportMetadataSourceServiceImpl.class); + + private static final String ENDPOINT_SEARCH = "https://vufind.org/demo/api/v1/search"; + private static final String ENDPOINT_RECORD = "https://vufind.org/demo/api/v1/record"; + + private String fields; + + @Autowired + private LiveImportClient liveImportClient; + + public VuFindImportMetadataSourceServiceImpl(String fields) { + this.fields = fields; + } + + @Override + public String getImportSource() { + return "VuFind"; + } + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + String records = retry(new GetByVuFindIdCallable(id, fields)); + List importRecords = extractMetadataFromRecordList(records); + return importRecords != null && !importRecords.isEmpty() ? importRecords.get(0) : null; + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + String records = retry(new SearchByQueryCallable(query, count, start, fields)); + return extractMetadataFromRecordList(records); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + String records = retry(new SearchByQueryCallable(query, fields)); + return extractMetadataFromRecordList(records); + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + String records = retry(new SearchByQueryCallable(query, fields)); + List importRecords = extractMetadataFromRecordList(records); + return importRecords != null && !importRecords.isEmpty() ? importRecords.get(0) : null; + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + String records = retry(new FindMatchingRecordsCallable(query)); + return extractMetadataFromRecordList(records); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for VuFind"); + } + + @Override + public void init() throws Exception {} + + private class CountByQueryCallable implements Callable { + + private Query query; + + public CountByQueryCallable(String queryString) { + query = new Query(); + query.addParameter("query", queryString); + } + + public CountByQueryCallable(Query query) { + this.query = query; + } + + @Override + public Integer call() throws Exception { + Integer start = 0; + Integer count = 1; + int page = start / count + 1; + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_SEARCH); + uriBuilder.addParameter("type", "AllField"); + uriBuilder.addParameter("page", String.valueOf(page)); + uriBuilder.addParameter("limit", count.toString()); + uriBuilder.addParameter("prettyPrint", String.valueOf(true)); + uriBuilder.addParameter("lookfor", query.getParameterAsClass("query", String.class)); + String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + ReadContext ctx = JsonPath.parse(responseString); + return ctx.read("$.resultCount"); + } + } + + private class GetByVuFindIdCallable implements Callable { + + private String id; + + private String fields; + + public GetByVuFindIdCallable(String id, String fields) { + this.id = id; + if (fields != null && fields.length() > 0) { + this.fields = fields; + } else { + this.fields = null; + } + } + + @Override + public String call() throws Exception { + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_RECORD); + uriBuilder.addParameter("id", id); + uriBuilder.addParameter("prettyPrint", "false"); + if (StringUtils.isNotBlank(fields)) { + for (String field : fields.split(",")) { + uriBuilder.addParameter("field[]", field); + } + } + String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + return response; + } + } + + private class SearchByQueryCallable implements Callable { + + private Query query; + + private String fields; + + public SearchByQueryCallable(String queryString, Integer maxResult, Integer start, String fields) { + query = new Query(); + query.addParameter("query", queryString); + query.addParameter("count", maxResult); + query.addParameter("start", start); + if (StringUtils.isNotBlank(fields)) { + this.fields = fields; + } else { + this.fields = null; + } + } + + public SearchByQueryCallable(Query query, String fields) { + this.query = query; + if (StringUtils.isNotBlank(fields)) { + this.fields = fields; + } else { + this.fields = null; + } + } + + @Override + public String call() throws Exception { + Integer start = query.getParameterAsClass("start", Integer.class); + Integer count = query.getParameterAsClass("count", Integer.class); + int page = count != 0 ? start / count : 0; + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_SEARCH); + uriBuilder.addParameter("type", "AllField"); + //page looks 1 based (start = 0, count = 20 -> page = 0) + uriBuilder.addParameter("page", String.valueOf(page + 1)); + uriBuilder.addParameter("limit", count.toString()); + uriBuilder.addParameter("prettyPrint", String.valueOf(true)); + uriBuilder.addParameter("lookfor", query.getParameterAsClass("query", String.class)); + if (StringUtils.isNotBlank(fields)) { + for (String field : fields.split(",")) { + uriBuilder.addParameter("field[]", field); + } + } + return liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), new HashMap()); + } + + } + + public class FindMatchingRecordsCallable implements Callable { + + private Query query; + + private String fields; + + public FindMatchingRecordsCallable(Query query) { + this.query = query; + } + + @Override + public String call() throws Exception { + String author = query.getParameterAsClass("author", String.class); + String title = query.getParameterAsClass("title", String.class); + Integer start = query.getParameterAsClass("start", Integer.class); + Integer count = query.getParameterAsClass("count", Integer.class); + int page = count != 0 ? start / count : 0; + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_RECORD); + uriBuilder.addParameter("type", "AllField"); + //pagination is 1 based (first page: start = 0, count = 20 -> page = 0 -> +1 = 1) + uriBuilder.addParameter("page", String.valueOf(page ++)); + uriBuilder.addParameter("limit", count.toString()); + uriBuilder.addParameter("prettyPrint", "true"); + if (fields != null && !fields.isEmpty()) { + for (String field : fields.split(",")) { + uriBuilder.addParameter("field[]", field); + } + } + String filter = StringUtils.EMPTY; + if (StringUtils.isNotBlank(author)) { + filter = "author:" + author; + } + if (StringUtils.isNotBlank(title)) { + if (StringUtils.isNotBlank(filter)) { + filter = filter + " AND title:" + title; + } else { + filter = "title:" + title; + } + } + uriBuilder.addParameter("lookfor", filter); + return liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), new HashMap()); + } + + } + + private List extractMetadataFromRecordList(String records) { + List recordsResult = new ArrayList<>(); + ReadContext ctx = JsonPath.parse(records); + try { + Object o = ctx.read("$.records[*]"); + 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); + recordsResult.add(transformSourceRecords(innerJson)); + } + } else { + recordsResult.add(transformSourceRecords(o.toString())); + } + } catch (Exception e) { + log.error("Error reading data from VuFind " + e.getMessage(), e); + } + return recordsResult; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/vufind/metadatamapping/VuFindFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/vufind/metadatamapping/VuFindFieldMapping.java new file mode 100644 index 0000000000..baed54f5d7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/vufind/metadatamapping/VuFindFieldMapping.java @@ -0,0 +1,32 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.vufind.metadatamapping; + +import java.util.Map; +import javax.annotation.Resource; + +import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; + +@SuppressWarnings("rawtypes") +public class VuFindFieldMapping 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 = "vufindMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index 96149c581c..8a75b1b14d 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 @@ -115,12 +115,18 @@ - + + + + + + + diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 90da4ed0c3..83d7a27738 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -105,4 +105,15 @@ + + + + + + + Publication + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/vufind-integration.xml b/dspace/config/spring/api/vufind-integration.xml new file mode 100644 index 0000000000..094f3207a9 --- /dev/null +++ b/dspace/config/spring/api/vufind-integration.xml @@ -0,0 +1,155 @@ + + + + + + + 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 06b4b219211438d3126a45101cbea4942af60a69 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 4 Apr 2022 12:04:51 +0200 Subject: [PATCH 0800/1254] [CST-5306] Minor improvements in ResearcherProfileServiceImpl --- .../profile/ResearcherProfileServiceImpl.java | 78 +++++++++---------- .../test/data/dspaceFolder/config/local.cfg | 2 +- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java index 75c1db45b0..19c2f03e5e 100644 --- a/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/profile/ResearcherProfileServiceImpl.java @@ -7,6 +7,9 @@ */ package org.dspace.app.profile; +import static java.util.Optional.empty; +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.ArrayUtils.contains; import static org.dspace.content.authority.Choices.CF_ACCEPTED; import static org.dspace.core.Constants.READ; import static org.dspace.eperson.Group.ANONYMOUS; @@ -14,10 +17,9 @@ import static org.dspace.eperson.Group.ANONYMOUS; import java.io.IOException; import java.net.URI; import java.sql.SQLException; -import java.util.Arrays; import java.util.Iterator; import java.util.List; -import java.util.Objects; +import java.util.Optional; import java.util.UUID; import org.apache.commons.collections4.CollectionUtils; @@ -107,18 +109,19 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { throw new ResourceConflictException("A profile is already linked to the provided User", profile); } - Collection collection = findProfileCollection(context); - if (collection == null) { - throw new IllegalStateException("No collection found for researcher profiles"); - } + Collection collection = findProfileCollection(context) + .orElseThrow(() -> new IllegalStateException("No collection found for researcher profiles")); context.turnOffAuthorisationSystem(); - Item item = createProfileItem(context, ePerson, collection); - context.restoreAuthSystemState(); + try { - ResearcherProfile researcherProfile = new ResearcherProfile(item); + Item item = createProfileItem(context, ePerson, collection); + return new ResearcherProfile(item); + + } finally { + context.restoreAuthSystemState(); + } - return researcherProfile; } @Override @@ -160,25 +163,21 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { @Override public ResearcherProfile claim(final Context context, final EPerson ePerson, final URI uri) throws SQLException, AuthorizeException, SearchServiceException { + Item profileItem = findResearcherProfileItemById(context, ePerson.getID()); if (profileItem != null) { ResearcherProfile profile = new ResearcherProfile(profileItem); throw new ResourceConflictException("A profile is already linked to the provided User", profile); } - Collection collection = findProfileCollection(context); - if (collection == null) { - throw new IllegalStateException("No collection found for researcher profiles"); - } + Item item = findItemByURI(context, uri) + .orElseThrow(() -> new IllegalArgumentException("No item found by URI " + uri)); - final String path = uri.getPath(); - final UUID uuid = UUIDUtils.fromString(path.substring(path.lastIndexOf("/") + 1 )); - Item item = itemService.find(context, uuid); - if (Objects.isNull(item) || !item.isArchived() || item.isWithdrawn() || notClaimableEntityType(item)) { + if (!item.isArchived() || item.isWithdrawn() || notClaimableEntityType(item)) { throw new IllegalArgumentException("Provided uri does not represent a valid Item to be claimed"); } - final String existingOwner = itemService.getMetadataFirstValue(item, "dspace", "object", - "owner", null); + + String existingOwner = itemService.getMetadataFirstValue(item, "dspace", "object", "owner", Item.ANY); if (StringUtils.isNotBlank(existingOwner)) { throw new IllegalArgumentException("Item with provided uri has already an owner"); @@ -192,10 +191,15 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { return new ResearcherProfile(item); } + private Optional findItemByURI(final Context context, final URI uri) throws SQLException { + String path = uri.getPath(); + UUID uuid = UUIDUtils.fromString(path.substring(path.lastIndexOf("/") + 1)); + return ofNullable(itemService.find(context, uuid)); + } + private boolean notClaimableEntityType(final Item item) { - final String entityType = itemService.getEntityType(item); - return Arrays.stream(configurationService.getArrayProperty("claimable.entityType")) - .noneMatch(entityType::equals); + String entityType = itemService.getEntityType(item); + return !contains(configurationService.getArrayProperty("claimable.entityType"), entityType); } private Item findResearcherProfileItemById(Context context, UUID id) throws SQLException, AuthorizeException { @@ -205,18 +209,20 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { Iterator items = itemService.findByAuthorityValue(context, "dspace", "object", "owner", id.toString()); while (items.hasNext()) { Item item = items.next(); - if (hasEntityTypeMetadataEqualsTo(item, profileType)) { + String entityType = itemService.getEntityType(item); + if (profileType.equals(entityType)) { return item; } } + return null; } @SuppressWarnings("rawtypes") - private Collection findProfileCollection(Context context) throws SQLException, SearchServiceException { + private Optional findProfileCollection(Context context) throws SQLException, SearchServiceException { UUID uuid = UUIDUtils.fromString(configurationService.getProperty("researcher-profile.collection.uuid")); if (uuid != null) { - return collectionService.find(context, uuid); + return ofNullable(collectionService.find(context, uuid)); } String profileType = getProfileType(); @@ -229,28 +235,27 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { List indexableObjects = discoverResult.getIndexableObjects(); if (CollectionUtils.isEmpty(indexableObjects)) { - return null; + return empty(); } if (indexableObjects.size() > 1) { log.warn("Multiple " + profileType + " type collections were found during profile creation"); - return null; + return empty(); } - return (Collection) indexableObjects.get(0).getIndexedObject(); + return ofNullable((Collection) indexableObjects.get(0).getIndexedObject()); } private Item createProfileItem(Context context, EPerson ePerson, Collection collection) throws AuthorizeException, SQLException { String id = ePerson.getID().toString(); + String fullName = ePerson.getFullName(); WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, true); Item item = workspaceItem.getItem(); - itemService.addMetadata(context, item, "dc", "title", null, null, - ePerson.getFullName()); - itemService.addMetadata(context, item, "dspace", "object", "owner", null, - ePerson.getFullName(), id, CF_ACCEPTED); + itemService.addMetadata(context, item, "dc", "title", null, null, fullName); + itemService.addMetadata(context, item, "dspace", "object", "owner", null, fullName, id, CF_ACCEPTED); item = installItemService.installItem(context, workspaceItem); @@ -261,13 +266,6 @@ public class ResearcherProfileServiceImpl implements ResearcherProfileService { return item; } - private boolean hasEntityTypeMetadataEqualsTo(Item item, String entityType) { - return item.getMetadata().stream().anyMatch(metadataValue -> { - return "dspace.entity.type".equals(metadataValue.getMetadataField().toString('.')) && - entityType.equals(metadataValue.getValue()); - }); - } - private boolean isHardDeleteEnabled() { return configurationService.getBooleanProperty("researcher-profile.hard-delete.enabled"); } diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 34acade122..a9f606c180 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -20,6 +20,7 @@ # For example, including "dspace.dir" in this local.cfg will override the # default value of "dspace.dir" in the dspace.cfg file. # + ########################## # SERVER CONFIGURATION # ########################## @@ -144,5 +145,4 @@ useProxies = true proxies.trusted.ipranges = 7.7.7.7 proxies.trusted.include_ui_ip = true -# researcher-profile.type researcher-profile.type = Person \ No newline at end of file From e0913ccc5c7574f2f7319914daad794c6741c42f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 4 Apr 2022 15:02:29 +0200 Subject: [PATCH 0801/1254] [CST-5303] porting of Scielo live import service --- .../SimpleRisToMetadataConcatContributor.java | 59 +++++ .../SimpleRisToMetadataContributor.java | 71 ++++++ .../scielo/service/ScieloFieldMapping.java | 37 +++ ...ScieloImportMetadataSourceServiceImpl.java | 230 ++++++++++++++++++ .../spring-dspace-addon-import-services.xml | 5 + .../config/spring/api/external-services.xml | 12 +- 6 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleRisToMetadataConcatContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleRisToMetadataContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleRisToMetadataConcatContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleRisToMetadataConcatContributor.java new file mode 100644 index 0000000000..5dd354c6f1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleRisToMetadataConcatContributor.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.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; + +/** + * This contributor extends SimpleRisToMetadataContributor, + * in particular, this one is able to chain multi values into a single one + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class SimpleRisToMetadataConcatContributor extends SimpleRisToMetadataContributor { + + private String tag; + + private MetadataFieldConfig metadata; + + @Override + public Collection contributeMetadata(Map> record) { + List values = new LinkedList<>(); + List fieldValues = record.get(this.tag); + Optional.ofNullable(fieldValues) + .map(fv -> fv.stream()) + .map(s -> s.collect(Collectors.joining(" "))) + .ifPresent(t -> values.add(this.metadataFieldMapping.toDCValue(this.metadata, t))); + return values; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public MetadataFieldConfig getMetadata() { + return metadata; + } + + public void setMetadata(MetadataFieldConfig metadata) { + this.metadata = metadata; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleRisToMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleRisToMetadataContributor.java new file mode 100644 index 0000000000..36ea0dd478 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleRisToMetadataContributor.java @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; + +/** + * Metadata contributor that takes a record defined as Map> + * and turns it into metadatums configured in fieldToMetadata + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class SimpleRisToMetadataContributor implements MetadataContributor>> { + + protected Map fieldToMetadata; + + protected MetadataFieldMapping>, + MetadataContributor>>> metadataFieldMapping; + + public SimpleRisToMetadataContributor() {} + + public SimpleRisToMetadataContributor(Map fieldToMetadata) { + this.fieldToMetadata = fieldToMetadata; + } + + @Override + public Collection contributeMetadata(Map> record) { + List values = new LinkedList<>(); + for (String field : fieldToMetadata.keySet()) { + List fieldValues = record.get(field); + if (Objects.nonNull(fieldValues)) { + for (String value : fieldValues) { + values.add(metadataFieldMapping.toDCValue(fieldToMetadata.get(field), value)); + } + } + } + return values; + } + + public Map getFieldToMetadata() { + return fieldToMetadata; + } + + public void setFieldToMetadata(Map fieldToMetadata) { + this.fieldToMetadata = fieldToMetadata; + } + + public MetadataFieldMapping>, + MetadataContributor>>> getMetadataFieldMapping() { + return metadataFieldMapping; + } + + public void setMetadataFieldMapping(MetadataFieldMapping>, + MetadataContributor>>> metadataFieldMapping) { + this.metadataFieldMapping = metadataFieldMapping; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java new file mode 100644 index 0000000000..0d7183a1f0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloFieldMapping.java @@ -0,0 +1,37 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.scielo.service; +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 Scielo metadatum fields on the DSpace metadatum fields + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4science dot it) + */ +@SuppressWarnings("rawtypes") +public class ScieloFieldMapping 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 + @SuppressWarnings("unchecked") + @Resource(name = "scieloMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java new file mode 100644 index 0000000000..2ebe520fde --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/scielo/service/ScieloImportMetadataSourceServiceImpl.java @@ -0,0 +1,230 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.scielo.service; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.el.MethodNotFoundException; +import javax.ws.rs.BadRequestException; + +import org.apache.http.client.utils.URIBuilder; +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.FileSourceException; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.scopus.service.LiveImportClient; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.components.QuerySource; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements a data source for querying Scielo + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4Science dot it) + */ +public class ScieloImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService>> + implements QuerySource { + + private static final String ENDPOINT_SEARCH_SCIELO = "https://search.scielo.org/?output=ris&q="; + + private static final String PATTERN = "^([A-Z][A-Z0-9]) - (.*)$"; + + private static final String ID_PATTERN = "^(.....)-(.*)-(...)$"; + + private int timeout = 1000; + + @Autowired + private LiveImportClient liveImportClient; + + @Override + public void init() throws Exception {} + + @Override + public String getImportSource() { + return "scielo"; + } + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query, count, start)); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = retry(new SearchByQueryCallable(query)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + List records = retry(new FindByIdCallable(id)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new SearchNBByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for Scielo"); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for Scielo"); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for Scielo"); + } + + private class SearchNBByQueryCallable implements Callable { + + private String query; + + private SearchNBByQueryCallable(String queryString) { + this.query = queryString; + } + + private SearchNBByQueryCallable(Query query) { + this.query = query.getParameterAsClass("query", String.class); + } + + @Override + public Integer call() throws Exception { + String url = ENDPOINT_SEARCH_SCIELO + URLEncoder.encode(query, StandardCharsets.UTF_8); + String resp = liveImportClient.executeHttpGetRequest(timeout, url, new HashMap()); + Map>> records = getRecords(resp); + return Objects.nonNull(records.size()) ? records.size() : 0; + } + } + + private class FindByIdCallable implements Callable> { + + private String id; + + private FindByIdCallable(String id) { + this.id = id; + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + String scieloId = id.trim(); + Pattern risPattern = Pattern.compile(ID_PATTERN); + Matcher risMatcher = risPattern.matcher(scieloId); + if (risMatcher.matches()) { + String url = ENDPOINT_SEARCH_SCIELO + URLEncoder.encode(scieloId, StandardCharsets.UTF_8); + String resp = liveImportClient.executeHttpGetRequest(timeout, url, new HashMap()); + Map>> records = getRecords(resp); + if (Objects.nonNull(records) & !records.isEmpty()) { + results.add(transformSourceRecords(records.get(1))); + } + } else { + throw new BadRequestException("id provided : " + scieloId + " is not an ScieloID"); + } + return results; + } + } + + private class SearchByQueryCallable implements Callable> { + + private Query query; + + private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { + query = new Query(); + query.addParameter("query", queryString); + query.addParameter("start", start); + query.addParameter("count", maxResult); + } + + private SearchByQueryCallable(Query query) { + this.query = query; + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + String q = query.getParameterAsClass("query", String.class); + Integer count = query.getParameterAsClass("count", Integer.class); + Integer start = query.getParameterAsClass("start", Integer.class); + URIBuilder uriBuilder = new URIBuilder( + ENDPOINT_SEARCH_SCIELO + URLEncoder.encode(q, StandardCharsets.UTF_8)); + uriBuilder.addParameter("start", start.toString()); + uriBuilder.addParameter("count", count.toString()); + String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), + new HashMap()); + Map>> records = getRecords(resp); + for (int record : records.keySet()) { + results.add(transformSourceRecords(records.get(record))); + } + return results; + } + } + + private Map>> getRecords(String resp) throws FileSourceException { + Map>> records = new HashMap>>(); + BufferedReader reader; + int countRecord = 0; + try { + reader = new BufferedReader(new StringReader(resp)); + String line; + while ((line = reader.readLine()) != null) { + if (line.isEmpty() || line.equals("") || line.matches("^\\s*$")) { + continue; + } + line = line.replaceAll("\\uFEFF", "").trim(); + Pattern risPattern = Pattern.compile(PATTERN); + Matcher risMatcher = risPattern.matcher(line); + if (risMatcher.matches()) { + if (risMatcher.group(1).equals("TY") & risMatcher.group(2).equals("JOUR")) { + countRecord ++; + Map> newMap = new HashMap>(); + records.put(countRecord, newMap); + } else { + Map> tag2values = records.get(countRecord); + List values = tag2values.get(risMatcher.group(1)); + if (Objects.isNull(values)) { + List newValues = new ArrayList(); + newValues.add(risMatcher.group(2)); + tag2values.put(risMatcher.group(1), newValues); + } else { + values.add(risMatcher.group(2)); + tag2values.put(risMatcher.group(1), values); + } + } + } + } + } catch (Exception e) { + throw new FileSourceException("Cannot parse RIS file", e); + } + return records; + } + +} \ No newline at end of file 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 5e69ee9c42..7686986ae8 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 @@ -116,6 +116,11 @@ + + + + + diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 7f1295f839..2259ccb9f9 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -94,5 +94,15 @@ - + + + + + + + Publication + + + + \ No newline at end of file From dea0564201313487f2db7215ba7bd2fd7bd922f4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 4 Apr 2022 15:03:35 +0200 Subject: [PATCH 0802/1254] [CST-5303] added tests for Scielo live import service --- .../assetstore/sciello-single-record.txt | 24 ++ .../dspaceFolder/assetstore/scielo-test.txt | 51 ++++ .../ScieloImportMetadataSourceServiceIT.java | 241 ++++++++++++++++++ .../src/test/resources/test-config.properties | 3 + .../config/spring/api/scielo-integration.xml | 109 ++++++++ 5 files changed, 428 insertions(+) create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/sciello-single-record.txt create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/scielo-test.txt create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ScieloImportMetadataSourceServiceIT.java create mode 100644 dspace/config/spring/api/scielo-integration.xml diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/sciello-single-record.txt b/dspace-api/src/test/data/dspaceFolder/assetstore/sciello-single-record.txt new file mode 100644 index 0000000000..bd9934d2bc --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/sciello-single-record.txt @@ -0,0 +1,24 @@ +TY - JOUR +AU - Torres Marzo, Ricardo +TI - Requena Jiménez, Miguel, Los espacios de la muerte en Roma, Madrid, Síntesis, 2021, 365 págs. más bibliografía en línea, ISBN 978-84-135759-6-4. +JO - Nova tellus +J2 - Nova tellus +SN - 0185-3058 +VL - 39 +IS - 2 +DO - 10.19130/iifl.nt.2021.39.2.901 +DB - SciELO México +DP - http://www.scielo.org/ +ID - S0185-30582021000200231-mex +LA - es +SP - 231 +EP - 236 +DA - 2021-12 +PY - 2021 +UR - http://www.scielo.org.mx/scielo.php?script=sci_arttext&pid=S0185-30582021000200231&lang=pt +KW - Roma +KW - Historia +KW - ritos funerarios +KW - inframundo +KW - epitafios +ER - \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/scielo-test.txt b/dspace-api/src/test/data/dspaceFolder/assetstore/scielo-test.txt new file mode 100644 index 0000000000..4cc9d3ad36 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/scielo-test.txt @@ -0,0 +1,51 @@ +TY - JOUR +AU - Torres Marzo, Ricardo +TI - Requena Jiménez, Miguel, Los espacios de la muerte en Roma, Madrid, Síntesis, 2021, 365 págs. más bibliografía en línea, ISBN 978-84-135759-6-4. +JO - Nova tellus +J2 - Nova tellus +SN - 0185-3058 +VL - 39 +IS - 2 +DO - 10.19130/iifl.nt.2021.39.2.901 +DB - SciELO México +DP - http://www.scielo.org/ +ID - S0185-30582021000200231-mex +LA - es +SP - 231 +EP - 236 +DA - 2021-12 +PY - 2021 +UR - http://www.scielo.org.mx/scielo.php?script=sci_arttext&pid=S0185-30582021000200231&lang=pt +KW - Roma +KW - Historia +KW - ritos funerarios +KW - inframundo +KW - epitafios +ER - + +TY - JOUR +AU - MAGRI, GEO +TI - Rinegoziazione e revisione del contratto. Tribunale di Roma, Sez. VI, 27 agosto 2020 +JO - Revista de Derecho Privado +J2 - Rev. Derecho Privado +SN - 0123-4366 +VL - +IS - 41 +DO - 10.18601/01234366.n41.14 +DB - SciELO Colômbia +DP - http://www.scielo.org/ +ID - S0123-43662021000200397-col +LA - it +SP - 397 +EP - 418 +DA - 2021-12 +PY - 2021 +AB - ABSTRACT: The Tribunal of Rome imposes an obligation to renegotiate long-term contracts, the balance of which has been modified by the covro pandemic. The decision establishes a general obligation for the parties to execute the contract in good faith and gives the judge the possibility of a judicial review. This is a long-awaited decision in doctrine which complies with the indications of the Supreme Court of Cassation expressed in its memorandum 56/2020. +UR - http://www.scielo.org.co/scielo.php?script=sci_arttext&pid=S0123-43662021000200397&lang=pt +L1 - http://www.scielo.org.co/pdf/rdp/n41/0123-4366-rdp-41-397.pdf +KW - sopravvenienza contrattuale +KW - covro +KW - buona fede in senso oggettivo +KW - obbligo di rinegoziare +KW - revisione del contratto +ER - \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScieloImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScieloImportMetadataSourceServiceIT.java new file mode 100644 index 0000000000..c86c6cfd72 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScieloImportMetadataSourceServiceIT.java @@ -0,0 +1,241 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +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.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.scielo.service.ScieloImportMetadataSourceServiceImpl; +import org.dspace.importer.external.scopus.service.LiveImportClientImpl; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link ScieloImportMetadataSourceServiceImpl} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class ScieloImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Autowired + private ScieloImportMetadataSourceServiceImpl scieloServiceImpl; + + @Test + public void scieloImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.scielo").toString(); + try (FileInputStream scieloResp = new FileInputStream(path)) { + + String scieloRipResp = IOUtils.toString(scieloResp, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(scieloRipResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection collection2match = getRecords(); + Collection recordsImported = scieloServiceImpl.getRecords("test query", 0, 2); + assertEquals(2, recordsImported.size()); + assertTrue(matchRecords(recordsImported, collection2match)); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + @Test + public void scieloImportMetadataGetRecordsCountTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.scielo").toString(); + try (FileInputStream file = new FileInputStream(path)) { + String scieloResp = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(scieloResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + int tot = scieloServiceImpl.getRecordsCount("test query"); + assertEquals(2, tot); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + @Test(expected = MethodNotFoundException.class) + public void scieloImportMetadataFindMatchingRecordsTest() 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(); + scieloServiceImpl.findMatchingRecords(testItem); + } + + @Test(expected = MethodNotFoundException.class) + public void scieloImportMetadataGetRecordsCountByQueryTest() throws Exception { + Query q = new Query(); + q.addParameter("query", "test query"); + scieloServiceImpl.getRecordsCount(q); + } + + @Test + public void scieloImportMetadataGetRecordsByIdTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.sciello-single").toString(); + try (FileInputStream scieloResp = new FileInputStream(path)) { + + String scieloRipResp = IOUtils.toString(scieloResp, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(scieloRipResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection collection2match = getRecords(); + Collection firstRecord = Arrays.asList(collection2match.iterator().next()); + ImportRecord record = scieloServiceImpl.getRecord("S0185-30582021000200231-mex"); + assertNotNull(record); + Collection recordsImported = Arrays.asList(record); + assertTrue(matchRecords(recordsImported, firstRecord)); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + private Collection getRecords() { + Collection records = new LinkedList(); + //define first record + List metadatums = new ArrayList(); + MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", "Nova tellus"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2021"); + MetadatumDTO citation = createMetadatumDTO("oaire", "citation", "issue", "2"); + MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.19130/iifl.nt.2021.39.2.901"); + MetadatumDTO endPage = createMetadatumDTO("oaire", "citation", "endPage", "236"); + MetadatumDTO subject = createMetadatumDTO("dc", "subject", null, "Roma"); + MetadatumDTO subject2 = createMetadatumDTO("dc", "subject", null, "Historia"); + MetadatumDTO subject3 = createMetadatumDTO("dc", "subject", null, "ritos funerarios"); + MetadatumDTO subject4 = createMetadatumDTO("dc", "subject", null, "inframundo"); + MetadatumDTO subject5 = createMetadatumDTO("dc", "subject", null, "epitafios"); + MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "Torres Marzo, Ricardo"); + MetadatumDTO title = createMetadatumDTO("dc", "title", null, "Requena Jiménez, Miguel, Los espacios" + + " de la muerte en Roma, Madrid, Síntesis, 2021, 365 págs." + + " más bibliografía en línea, ISBN 978-84-135759-6-4."); + MetadatumDTO volume = createMetadatumDTO("oaire", "citation", "volume", "39"); + MetadatumDTO issn = createMetadatumDTO("dc", "identifier", "issn", "0185-3058"); + MetadatumDTO other = createMetadatumDTO("dc", "identifier", "other", "S0185-30582021000200231-mex"); + MetadatumDTO startPage = createMetadatumDTO("oaire", "citation", "startPage", "231"); + + metadatums.add(ispartof); + metadatums.add(date); + metadatums.add(citation); + metadatums.add(doi); + metadatums.add(endPage); + metadatums.add(subject); + metadatums.add(subject2); + metadatums.add(subject3); + metadatums.add(subject4); + metadatums.add(subject5); + metadatums.add(author); + metadatums.add(title); + metadatums.add(volume); + metadatums.add(issn); + metadatums.add(other); + metadatums.add(startPage); + + ImportRecord firstrRecord = new ImportRecord(metadatums); + + //define second record + List metadatums2 = new ArrayList(); + MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", "Revista de Derecho Privado"); + MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2021"); + MetadatumDTO citation2 = createMetadatumDTO("oaire", "citation", "issue", "41"); + MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.18601/01234366.n41.14"); + MetadatumDTO endPage2 = createMetadatumDTO("oaire", "citation", "endPage", "418"); + MetadatumDTO subject6 = createMetadatumDTO("dc", "subject", null, "sopravvenienza contrattuale"); + MetadatumDTO subject7 = createMetadatumDTO("dc", "subject", null, "covro"); + MetadatumDTO subject8 = createMetadatumDTO("dc", "subject", null, "buona fede in senso oggettivo"); + MetadatumDTO subject9 = createMetadatumDTO("dc", "subject", null, "obbligo di rinegoziare"); + MetadatumDTO subject10 = createMetadatumDTO("dc", "subject", null, "revisione del contratto"); + MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "MAGRI, GEO"); + MetadatumDTO title2 = createMetadatumDTO("dc", "title", null, + "Rinegoziazione e revisione del contratto. Tribunale di Roma, Sez. VI, 27 agosto 2020"); + MetadatumDTO issn2 = createMetadatumDTO("dc", "identifier", "issn", "0123-4366"); + MetadatumDTO other2 = createMetadatumDTO("dc", "identifier", "other", "S0123-43662021000200397-col"); + MetadatumDTO startPage2 = createMetadatumDTO("oaire", "citation", "startPage", "397"); + MetadatumDTO description = createMetadatumDTO("dc", "description", "abstract", + "ABSTRACT: The Tribunal of Rome imposes an obligation to renegotiate long-term contracts," + + " the balance of which has been modified by the covro pandemic. The decision establishes a" + + " general obligation for the parties to execute the contract in good faith and gives the judge" + + " the possibility of a judicial review. This is a long-awaited decision in doctrine which complies" + + " with the indications of the Supreme Court of Cassation expressed in its memorandum 56/2020."); + + metadatums2.add(ispartof2); + metadatums2.add(date2); + metadatums2.add(citation2); + metadatums2.add(doi2); + metadatums2.add(endPage2); + metadatums2.add(subject6); + metadatums2.add(subject7); + metadatums2.add(subject8); + metadatums2.add(subject9); + metadatums2.add(subject10); + metadatums2.add(author2); + metadatums2.add(title2); + metadatums2.add(issn2); + metadatums2.add(other2); + metadatums2.add(startPage2); + metadatums2.add(description); + + ImportRecord secondRecord = new ImportRecord(metadatums2); + records.add(firstrRecord); + records.add(secondRecord); + return records; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/test-config.properties b/dspace-server-webapp/src/test/resources/test-config.properties index 3af96b20fc..a2a5160d9c 100644 --- a/dspace-server-webapp/src/test/resources/test-config.properties +++ b/dspace-server-webapp/src/test/resources/test-config.properties @@ -14,3 +14,6 @@ test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf #Path for a test Taskfile for the curate script test.curateTaskFile = ./target/testing/dspace/assetstore/curate.txt + +test.scielo = ./target/testing/dspace/assetstore/scielo-test.txt +test.sciello-single = ./target/testing/dspace/assetstore/sciello-single-record.txt \ No newline at end of file diff --git a/dspace/config/spring/api/scielo-integration.xml b/dspace/config/spring/api/scielo-integration.xml new file mode 100644 index 0000000000..05ee62c9c1 --- /dev/null +++ b/dspace/config/spring/api/scielo-integration.xml @@ -0,0 +1,109 @@ + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From fb31905ed5f7bb20f014d142395cef8f3ef11ee4 Mon Sep 17 00:00:00 2001 From: eskander Date: Mon, 4 Apr 2022 15:58:06 +0200 Subject: [PATCH 0803/1254] [CST-5534] canSynchronizeWithORCID Authorization feature. --- .../impl/CanSynchronizeWithORCID.java | 73 ++++++++ .../CanSynchronizeWithORCIDIT.java | 162 ++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSynchronizeWithORCID.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSynchronizeWithORCIDIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSynchronizeWithORCID.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSynchronizeWithORCID.java new file mode 100644 index 0000000000..f719265a0b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSynchronizeWithORCID.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.authorization.impl; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Predicate; + +import org.apache.commons.lang3.StringUtils; +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.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The synchronization with ORCID feature. It can be used to verify + * if the user can synchronize with ORCID. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +@Component +@AuthorizationFeatureDocumentation(name = CanSynchronizeWithORCID.NAME, + description = "It can be used to verify if the user can synchronize with ORCID") +public class CanSynchronizeWithORCID implements AuthorizationFeature { + + public static final String NAME = "canSynchronizeWithORCID"; + + @Autowired + private ItemService itemService; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + + EPerson ePerson = context.getCurrentUser(); + + if (!(object instanceof ItemRest) || Objects.isNull(ePerson)) { + return false; + } + + String id = ((ItemRest) object).getId(); + Item item = itemService.find(context, UUID.fromString(id)); + + return isDspaceObjectOwner(ePerson, item); + } + + @Override + public String[] getSupportedTypes() { + return new String[] { ItemRest.CATEGORY + "." + ItemRest.NAME }; + } + + private boolean isDspaceObjectOwner(EPerson eperson, Item item) { + if (eperson == null) { + return false; + } + List owners = itemService.getMetadataByMetadataString(item, "dspace.object.owner"); + Predicate checkOwner = v -> StringUtils.equals(v.getAuthority(), eperson.getID().toString()); + return owners.stream().anyMatch(checkOwner); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSynchronizeWithORCIDIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSynchronizeWithORCIDIT.java new file mode 100644 index 0000000000..a9e7350a67 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSynchronizeWithORCIDIT.java @@ -0,0 +1,162 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.greaterThanOrEqualTo; +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 org.dspace.app.rest.authorization.impl.CanSynchronizeWithORCID; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.eperson.EPerson; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test for the canSynchronizeWithORCID authorization feature. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.com) + */ +public class CanSynchronizeWithORCIDIT extends AbstractControllerIntegrationTest { + + @Autowired + private Utils utils; + + @Autowired + private ItemConverter itemConverter; + + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + + final String feature = "canSynchronizeWithORCID"; + private Item itemA; + private ItemRest itemARest; + private Community communityA; + private Collection collectionA; + private AuthorizationFeature canSynchronizeWithORCID; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + + canSynchronizeWithORCID = authorizationFeatureService.find(CanSynchronizeWithORCID.NAME); + + communityA = CommunityBuilder.createCommunity(context) + .withName("communityA").build(); + + collectionA = CollectionBuilder.createCollection(context, communityA) + .withName("collectionA").build(); + + itemA = ItemBuilder.createItem(context, collectionA) + .withTitle("itemA") + .withDspaceObjectOwner("user" , context.getCurrentUser().getID().toString()) + .build(); + + context.restoreAuthSystemState(); + + itemARest = itemConverter.convert(itemA, Projection.DEFAULT); + } + + @Test + public void anonymousHasNotAccessTest() throws Exception { + getClient().perform(get("/api/authz/authorizations/search/object") + .param("embed", "feature") + .param("feature", feature) + .param("uri", utils.linkToSingleResource(itemARest, "self").getHref())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void testCanSynchronizeWithORCIDIfItemDoesNotHasDspaceObjectOwner() throws Exception { + + EPerson user = context.getCurrentUser(); + + context.turnOffAuthorisationSystem(); + + Item item = ItemBuilder.createItem(context, collectionA) + .withTitle("item") + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(user.getEmail(), password); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(item)) + .param("eperson", user.getID().toString()) + .param("feature", canSynchronizeWithORCID.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void testCanSynchronizeWithORCIDIfItemHasDspaceObjectOwnerOfAnotherUUID() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson anotherUser = EPersonBuilder.createEPerson(context) + .withEmail("user@example.com") + .withPassword(password) + .build(); + + context.restoreAuthSystemState(); + + String token = getAuthToken(anotherUser.getEmail(), password); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(itemA)) + .param("eperson", anotherUser.getID().toString()) + .param("feature", canSynchronizeWithORCID.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded").doesNotExist()); + } + + @Test + public void testCanSynchronizeWithORCIDIfItemHasDspaceObjectOwner() throws Exception { + + EPerson user = context.getCurrentUser(); + + String token = getAuthToken(user.getEmail(), password); + + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", uri(itemA)) + .param("eperson", user.getID().toString()) + .param("feature", canSynchronizeWithORCID.getName())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))); + } + + private String uri(Item item) { + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); + String itemRestURI = utils.linkToSingleResource(itemRest, "self").getHref(); + return itemRestURI; + } + +} From 2bb9883b888455ad21e5e3632dd1538ad412a1dd Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 25 Mar 2022 15:42:17 +0100 Subject: [PATCH 0804/1254] [CST-5303] implemented http live import client --- .../scopus/service/LiveImportClient.java | 22 +++++ .../scopus/service/LiveImportClientImpl.java | 94 +++++++++++++++++++ .../config/spring/api/external-services.xml | 2 + 3 files changed, 118 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.java new file mode 100644 index 0000000000..50006fd486 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClient.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.importer.external.scopus.service; + +import java.io.InputStream; +import java.util.Map; + +/** + * Interface for classes that allow to contact LiveImport clients. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public interface LiveImportClient { + + public InputStream executeHttpGetRequest(int timeout, String URL, Map requestParams); + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java new file mode 100644 index 0000000000..f11e2fc4f2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.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.importer.external.scopus.service; + +import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.config.RequestConfig.Builder; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.DefaultProxyRoutePlanner; +import org.apache.log4j.Logger; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link LiveImportClient}. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science dot com) + */ +public class LiveImportClientImpl implements LiveImportClient { + + private static final Logger log = Logger.getLogger(LiveImportClientImpl.class); + + @Autowired + private ConfigurationService configurationService; + + @Override + public InputStream executeHttpGetRequest(int timeout, String URL, Map requestParams) { + HttpGet method = null; + String proxyHost = configurationService.getProperty("http.proxy.host"); + String proxyPort = configurationService.getProperty("http.proxy.port"); + try { + HttpClientBuilder hcBuilder = HttpClients.custom(); + Builder requestConfigBuilder = RequestConfig.custom(); + requestConfigBuilder.setConnectionRequestTimeout(timeout); + + if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) { + HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http"); + DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); + hcBuilder.setRoutePlanner(routePlanner); + } + + method = new HttpGet(getSearchUrl(URL, requestParams)); + method.setConfig(requestConfigBuilder.build()); + + HttpClient client = hcBuilder.build(); + HttpResponse httpResponse = client.execute(method); + if (isNotSuccessfull(httpResponse)) { + throw new RuntimeException(); + } + return httpResponse.getEntity().getContent(); + } catch (Exception e1) { + log.error(e1.getMessage(), e1); + } finally { + if (Objects.nonNull(method)) { + method.releaseConnection(); + } + } + return null; + } + + private String getSearchUrl(String URL, Map requestParams) throws URISyntaxException { + URIBuilder uriBuilder = new URIBuilder(URL); + for (String param : requestParams.keySet()) { + uriBuilder.setParameter(param, requestParams.get(param)); + } + return uriBuilder.toString(); + } + + private boolean isNotSuccessfull(HttpResponse response) { + int statusCode = getStatusCode(response); + return statusCode < 200 || statusCode > 299; + } + + private int getStatusCode(HttpResponse response) { + return response.getStatusLine().getStatusCode(); + } + +} \ No newline at end of file diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 9e28e5d559..7f1295f839 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -5,6 +5,8 @@ + + From d60567617bff6555472516e80eb286f50322b4c6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 29 Mar 2022 16:00:02 +0200 Subject: [PATCH 0805/1254] [CST-5303] porting of scopus live import services --- .../main/java/org/dspace/core/Constants.java | 6 + .../AuthorMetadataContributor.java | 148 +++++++ .../PageRangeXPathMetadataContributor.java | 98 +++++ ...laceCharacterXPathMetadataContributor.java | 66 +++ .../ReplaceFieldXPathMetadataContributor.java | 73 ++++ .../SimpleXpathMetadatumContributor.java | 6 +- .../scopus/service/LiveImportClient.java | 3 +- .../scopus/service/LiveImportClientImpl.java | 56 ++- .../scopus/service/ScopusFieldMapping.java | 38 ++ ...ScopusImportMetadataSourceServiceImpl.java | 388 ++++++++++++++++++ .../importer/external/service/DoiCheck.java | 47 +++ .../submit/lookup/MapConverterModifier.java | 115 ++++++ .../org/dspace/util/SimpleMapConverter.java | 45 ++ .../spring-dspace-addon-import-services.xml | 8 + .../config/spring/api/external-services.xml | 13 + .../mapConverter-openAccesFlag.properties | 1 + dspace/config/dspace.cfg | 13 + dspace/config/spring/api/crosswalks.xml | 13 + .../config/spring/api/external-services.xml | 11 + .../config/spring/api/scopus-integration.xml | 338 +++++++++++++++ 20 files changed, 1462 insertions(+), 24 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/PageRangeXPathMetadataContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ReplaceCharacterXPathMetadataContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ReplaceFieldXPathMetadataContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java create mode 100644 dspace-api/src/main/java/org/dspace/submit/lookup/MapConverterModifier.java create mode 100644 dspace-api/src/main/java/org/dspace/util/SimpleMapConverter.java create mode 100644 dspace/config/crosswalks/mapConverter-openAccesFlag.properties create mode 100644 dspace/config/spring/api/crosswalks.xml create mode 100644 dspace/config/spring/api/scopus-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/core/Constants.java b/dspace-api/src/main/java/org/dspace/core/Constants.java index f730ef6545..a46c9ad403 100644 --- a/dspace-api/src/main/java/org/dspace/core/Constants.java +++ b/dspace-api/src/main/java/org/dspace/core/Constants.java @@ -227,6 +227,12 @@ public class Constants { public static final String VIRTUAL_AUTHORITY_PREFIX = "virtual::"; + /** + * The value stored in nested metadata that were left empty to keep them in the + * same number than the parent leading metadata + */ + public static final String PLACEHOLDER_PARENT_METADATA_VALUE = "#PLACEHOLDER_PARENT_METADATA_VALUE#"; + /* * Label used by the special entity type assigned when no explicit assignment is defined */ 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 new file mode 100644 index 0000000000..009584e530 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java @@ -0,0 +1,148 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.metadatamapping.contributor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang.StringUtils; +import org.dspace.core.Constants; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jaxen.JaxenException; +import org.jdom2.Element; +import org.jdom2.Namespace; + +/** + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4science dot it) + */ +public class AuthorMetadataContributor extends SimpleXpathMetadatumContributor { + + private static final Namespace NAMESPACE = Namespace.getNamespace("http://www.w3.org/2005/Atom"); + + private MetadataFieldConfig orcid; + private MetadataFieldConfig scopusId; + private MetadataFieldConfig authname; + private MetadataFieldConfig affiliation; + + private Map affId2affName = new HashMap(); + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + List metadatums = null; + fillAffillation(element); + try { + List nodes = element.getChildren("author", NAMESPACE); + for (Element el : nodes) { + metadatums = getMetadataOfAuthors(el); + if (Objects.nonNull(metadatums)) { + for (MetadatumDTO metadatum : metadatums) { + values.add(metadatum); + } + } + } + } catch (JaxenException e) { + throw new RuntimeException(e); + } + return values; + } + + private List getMetadataOfAuthors(Element element) throws JaxenException { + List metadatums = new ArrayList(); + Element authname = element.getChild("authname", NAMESPACE); + Element scopusId = element.getChild("authid", NAMESPACE); + Element orcid = element.getChild("orcid", NAMESPACE); + Element afid = element.getChild("afid", NAMESPACE); + + metadatums.add(getMetadata(getElementValue(authname), this.authname)); + metadatums.add(getMetadata(getElementValue(scopusId), this.scopusId)); + metadatums.add(getMetadata(getElementValue(orcid), this.orcid)); + metadatums.add(getMetadata(StringUtils.isNotBlank(afid.getValue()) + ? this.affId2affName.get(afid.getValue()) + : null, this.affiliation)); + return metadatums; + } + + private String getElementValue(Element element) { + if (Objects.nonNull(element)) { + return element.getValue(); + } + return StringUtils.EMPTY; + } + + private MetadatumDTO getMetadata(String value, MetadataFieldConfig metadaConfig) { + MetadatumDTO metadata = new MetadatumDTO(); + if (StringUtils.isNotBlank(value)) { + metadata.setValue(value); + } else { + metadata.setValue(Constants.PLACEHOLDER_PARENT_METADATA_VALUE); + } + metadata.setElement(metadaConfig.getElement()); + metadata.setQualifier(metadaConfig.getQualifier()); + metadata.setSchema(metadaConfig.getSchema()); + return metadata; + } + + private void fillAffillation(Element element) { + try { + List nodes = element.getChildren("affiliation", NAMESPACE); + for (Element el : nodes) { + fillAffiliation2Name(el); + } + } catch (JaxenException e) { + throw new RuntimeException(e); + } + } + + private void fillAffiliation2Name(Element element) throws JaxenException { + Element affilationName = element.getChild("affilname", NAMESPACE); + Element affilationId = element.getChild("afid", NAMESPACE); + if (StringUtils.isNotBlank(affilationId.getValue()) | StringUtils.isNotBlank(affilationName.getValue())) { + affId2affName.put(affilationId.getValue(), affilationName.getValue()); + } + } + + public MetadataFieldConfig getAuthname() { + return authname; + } + + public void setAuthname(MetadataFieldConfig authname) { + this.authname = authname; + } + + public MetadataFieldConfig getOrcid() { + return orcid; + } + + public void setOrcid(MetadataFieldConfig orcid) { + this.orcid = orcid; + } + + public MetadataFieldConfig getScopusId() { + return scopusId; + } + + public void setScopusId(MetadataFieldConfig scopusId) { + this.scopusId = scopusId; + } + + public MetadataFieldConfig getAffiliation() { + return affiliation; + } + + public void setAffiliation(MetadataFieldConfig affiliation) { + this.affiliation = affiliation; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/PageRangeXPathMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/PageRangeXPathMetadataContributor.java new file mode 100644 index 0000000000..3b03e5b5b5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/PageRangeXPathMetadataContributor.java @@ -0,0 +1,98 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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 java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jdom2.Element; +import org.jdom2.Namespace; + +/** + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4science.com) + */ +public class PageRangeXPathMetadataContributor extends SimpleXpathMetadatumContributor { + + private MetadataFieldConfig startPageMetadata; + + private MetadataFieldConfig endPageMetadata; + + @Override + public Collection contributeMetadata(Element el) { + List values = new LinkedList<>(); + List metadatums = null; + for (String ns : prefixToNamespaceMapping.keySet()) { + List nodes = el.getChildren(query, Namespace.getNamespace(ns)); + for (Element element : nodes) { + metadatums = getMetadatum(element.getValue()); + if (Objects.nonNull(metadatums)) { + for (MetadatumDTO metadatum : metadatums) { + values.add(metadatum); + } + } + } + } + return values; + } + + private List getMetadatum(String value) { + List metadatums = new ArrayList(); + if (StringUtils.isBlank(value)) { + return null; + } + String [] range = value.split("-"); + if (range.length == 2) { + metadatums.add(setStartPage(range)); + metadatums.add(setEndPage(range)); + } else if (range.length != 0) { + metadatums.add(setStartPage(range)); + } + return metadatums; + } + + private MetadatumDTO setEndPage(String[] range) { + MetadatumDTO endPage = new MetadatumDTO(); + endPage.setValue(range[1]); + endPage.setElement(endPageMetadata.getElement()); + endPage.setQualifier(endPageMetadata.getQualifier()); + endPage.setSchema(endPageMetadata.getSchema()); + return endPage; + } + + private MetadatumDTO setStartPage(String[] range) { + MetadatumDTO startPage = new MetadatumDTO(); + startPage.setValue(range[0]); + startPage.setElement(startPageMetadata.getElement()); + startPage.setQualifier(startPageMetadata.getQualifier()); + startPage.setSchema(startPageMetadata.getSchema()); + return startPage; + } + + public MetadataFieldConfig getStartPageMetadata() { + return startPageMetadata; + } + + public void setStartPageMetadata(MetadataFieldConfig startPageMetadata) { + this.startPageMetadata = startPageMetadata; + } + + public MetadataFieldConfig getEndPageMetadata() { + return endPageMetadata; + } + + public void setEndPageMetadata(MetadataFieldConfig endPageMetadata) { + this.endPageMetadata = endPageMetadata; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ReplaceCharacterXPathMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ReplaceCharacterXPathMetadataContributor.java new file mode 100644 index 0000000000..9fb92348be --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ReplaceCharacterXPathMetadataContributor.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.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jdom2.Element; +import org.jdom2.Namespace; + +/** + * This contributor replace specific character in the metadata value. + * It is useful for some provider (e.g. Scopus) which use containing "/" character. + * Actually, "/" will never encode by framework in URL building. In the same ways, if we + * encode "/" -> %2F, it will be encoded by framework and become %252F. + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4science.com) + */ +public class ReplaceCharacterXPathMetadataContributor extends SimpleXpathMetadatumContributor { + + private char characterToBeReplaced; + + private char characterToReplaceWith; + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + for (String ns : prefixToNamespaceMapping.keySet()) { + List nodes = element.getChildren(query, Namespace.getNamespace(ns)); + for (Element el : nodes) { + values.add(getMetadatum(field, el.getValue())); + } + } + return values; + } + + private MetadatumDTO getMetadatum(MetadataFieldConfig field, String value) { + MetadatumDTO dcValue = new MetadatumDTO(); + if (Objects.isNull(field)) { + return null; + } + dcValue.setValue(value == null ? null : value.replace(characterToBeReplaced, characterToReplaceWith)); + dcValue.setElement(field.getElement()); + dcValue.setQualifier(field.getQualifier()); + dcValue.setSchema(field.getSchema()); + return dcValue; + } + + public void setCharacterToBeReplaced(int characterToBeReplaced) { + this.characterToBeReplaced = (char)characterToBeReplaced; + } + + public void setCharacterToReplaceWith(int characterToReplaceWith) { + this.characterToReplaceWith = (char)characterToReplaceWith; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ReplaceFieldXPathMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ReplaceFieldXPathMetadataContributor.java new file mode 100644 index 0000000000..15df3c6979 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/ReplaceFieldXPathMetadataContributor.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.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.util.SimpleMapConverter; +import org.jdom2.Element; +import org.jdom2.Namespace; + +/** + * This contributor replace metadata value + * if this matched in mapConverter-openAccesFlag.properties file + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4science.com) + */ +public class ReplaceFieldXPathMetadataContributor extends SimpleXpathMetadatumContributor { + + private static final String UNSPECIFIED = "Unspecified"; + + private SimpleMapConverter simpleMapConverter; + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + MetadatumDTO metadatum = null; + for (String ns : prefixToNamespaceMapping.keySet()) { + List nodes = element.getChildren(query, Namespace.getNamespace(ns)); + for (Element el : nodes) { + metadatum = getMetadatum(field, el.getValue()); + if (Objects.nonNull(metadatum)) { + values.add(metadatum); + } + } + } + return values; + } + + private MetadatumDTO getMetadatum(MetadataFieldConfig field, String value) { + String convertedValue = simpleMapConverter.getValue(value); + if (UNSPECIFIED.equals(convertedValue)) { + return null; + } + MetadatumDTO dcValue = new MetadatumDTO(); + if (Objects.isNull(field)) { + return null; + } + dcValue.setValue(convertedValue); + dcValue.setElement(field.getElement()); + dcValue.setQualifier(field.getQualifier()); + dcValue.setSchema(field.getSchema()); + return dcValue; + } + + public SimpleMapConverter getSimpleMapConverter() { + return simpleMapConverter; + } + + public void setSimpleMapConverter(SimpleMapConverter simpleMapConverter) { + this.simpleMapConverter = simpleMapConverter; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java index 65d6d66947..10a4ddd4d8 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java @@ -33,7 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author Roeland Dillen (roeland at atmire dot com) */ public class SimpleXpathMetadatumContributor implements MetadataContributor { - private MetadataFieldConfig field; + protected MetadataFieldConfig field; private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); @@ -79,7 +79,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor prefixToNamespaceMapping; + protected Map prefixToNamespaceMapping; /** * Initialize SimpleXpathMetadatumContributor with a query, prefixToNamespaceMapping and MetadataFieldConfig @@ -103,7 +103,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor requestParams); + public String executeHttpGetRequest(int timeout, String URL, Map requestParams); } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java index f11e2fc4f2..d5503831d3 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/LiveImportClientImpl.java @@ -9,20 +9,21 @@ package org.dspace.importer.external.scopus.service; import java.io.InputStream; import java.net.URISyntaxException; +import java.nio.charset.Charset; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.DefaultProxyRoutePlanner; import org.apache.log4j.Logger; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -36,34 +37,32 @@ public class LiveImportClientImpl implements LiveImportClient { private static final Logger log = Logger.getLogger(LiveImportClientImpl.class); + private CloseableHttpClient httpClient; + @Autowired private ConfigurationService configurationService; @Override - public InputStream executeHttpGetRequest(int timeout, String URL, Map requestParams) { + public String executeHttpGetRequest(int timeout, String URL, Map requestParams) { HttpGet method = null; - String proxyHost = configurationService.getProperty("http.proxy.host"); - String proxyPort = configurationService.getProperty("http.proxy.port"); - try { - HttpClientBuilder hcBuilder = HttpClients.custom(); + try (CloseableHttpClient httpClient = Optional.ofNullable(this.httpClient) + .orElseGet(HttpClients::createDefault)) { + Builder requestConfigBuilder = RequestConfig.custom(); requestConfigBuilder.setConnectionRequestTimeout(timeout); - - if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) { - HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http"); - DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); - hcBuilder.setRoutePlanner(routePlanner); - } + RequestConfig defaultRequestConfig = requestConfigBuilder.build(); method = new HttpGet(getSearchUrl(URL, requestParams)); - method.setConfig(requestConfigBuilder.build()); + method.setConfig(defaultRequestConfig); - HttpClient client = hcBuilder.build(); - HttpResponse httpResponse = client.execute(method); + configureProxy(method, defaultRequestConfig); + + HttpResponse httpResponse = httpClient.execute(method); if (isNotSuccessfull(httpResponse)) { throw new RuntimeException(); } - return httpResponse.getEntity().getContent(); + InputStream inputStream = httpResponse.getEntity().getContent(); + return IOUtils.toString(inputStream, Charset.defaultCharset()); } catch (Exception e1) { log.error(e1.getMessage(), e1); } finally { @@ -71,7 +70,18 @@ public class LiveImportClientImpl implements LiveImportClient { method.releaseConnection(); } } - return null; + return StringUtils.EMPTY; + } + + private void configureProxy(HttpGet method, RequestConfig defaultRequestConfig) { + String proxyHost = configurationService.getProperty("http.proxy.host"); + String proxyPort = configurationService.getProperty("http.proxy.port"); + if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) { + RequestConfig requestConfig = RequestConfig.copy(defaultRequestConfig) + .setProxy(new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http")) + .build(); + method.setConfig(requestConfig); + } } private String getSearchUrl(String URL, Map requestParams) throws URISyntaxException { @@ -91,4 +101,12 @@ public class LiveImportClientImpl implements LiveImportClient { return response.getStatusLine().getStatusCode(); } + public CloseableHttpClient getHttpClient() { + return httpClient; + } + + public void setHttpClient(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } + } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusFieldMapping.java new file mode 100644 index 0000000000..c8143339b4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusFieldMapping.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.scopus.service; + +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 Scopus metadatum fields on the DSpace metadatum fields + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ +public class ScopusFieldMapping 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 = "scopusMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} 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 new file mode 100644 index 0000000000..3b16b1993c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java @@ -0,0 +1,388 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.scopus.service; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.el.MethodNotFoundException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.DoiCheck; +import org.dspace.importer.external.service.components.QuerySource; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements a data source for querying Scopus + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science dot com) + */ +public class ScopusImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + + private int timeout = 1000; + + int itemPerPage = 25; + + private static final String ENDPOINT_SEARCH_SCOPUS = "https://api.elsevier.com/content/search/scopus"; + + private String apiKey; + private String instKey; + private String viewMode = "COMPLETE"; + + @Autowired + private LiveImportClient liveImportClient; + + public LiveImportClient getLiveImportClient() { + return liveImportClient; + } + + public void setLiveImportClient(LiveImportClient liveImportClient) { + this.liveImportClient = liveImportClient; + } + + @Override + public void init() throws Exception {} + + /** + * The string that identifies this import implementation. Preferable a URI + * + * @return the identifying uri + */ + @Override + public String getImportSource() { + return "scopus"; + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + if (isEID(query)) { + return retry(new FindByIdCallable(query)).size(); + } + if (DoiCheck.isDoi(query)) { + query = DoiCheck.purgeDoiValue(query); + } + return retry(new SearchNBByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + if (isEID(query.toString())) { + return retry(new FindByIdCallable(query.toString())).size(); + } + if (DoiCheck.isDoi(query.toString())) { + query.addParameter("query", DoiCheck.purgeDoiValue(query.toString())); + } + return retry(new SearchNBByQueryCallable(query)); + } + + @Override + public Collection getRecords(String query, int start, + int count) throws MetadataSourceException { + if (isEID(query)) { + return retry(new FindByIdCallable(query)); + } + if (DoiCheck.isDoi(query)) { + query = DoiCheck.purgeDoiValue(query); + } + return retry(new SearchByQueryCallable(query, count, start)); + } + + @Override + public Collection getRecords(Query query) + throws MetadataSourceException { + if (isEID(query.toString())) { + return retry(new FindByIdCallable(query.toString())); + } + if (DoiCheck.isDoi(query.toString())) { + query.addParameter("query", DoiCheck.purgeDoiValue(query.toString())); + } + return retry(new SearchByQueryCallable(query)); + } + + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = null; + if (DoiCheck.isDoi(query.toString())) { + query.addParameter("query", DoiCheck.purgeDoiValue(query.toString())); + } + if (isEID(query.toString())) { + records = retry(new FindByIdCallable(query.toString())); + } else { + records = retry(new SearchByQueryCallable(query)); + } + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public Collection findMatchingRecords(Item item) + throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for Scopus"); + } + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + List records = retry(new FindByIdCallable(id)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public Collection findMatchingRecords(Query query) + throws MetadataSourceException { + if (isEID(query.toString())) { + return retry(new FindByIdCallable(query.toString())); + } + if (DoiCheck.isDoi(query.toString())) { + query.addParameter("query", DoiCheck.purgeDoiValue(query.toString())); + } + return retry(new FindByQueryCallable(query)); + } + + private boolean isEID(String query) { + Pattern pattern = Pattern.compile("2-s2\\.0-\\d+"); + Matcher match = pattern.matcher(query); + if (match.matches()) { + return true; + } + return false; + } + + /** + * This class implements a callable to get the numbers of result + */ + private class SearchNBByQueryCallable implements Callable { + + private String query; + + private SearchNBByQueryCallable(String queryString) { + this.query = queryString; + } + + private SearchNBByQueryCallable(Query query) { + this.query = query.getParameterAsClass("query", String.class); + } + + @Override + public Integer call() throws Exception { + if (StringUtils.isNotBlank(apiKey)) { + // Execute the request. + Map requestParams = getRequestParameters(query, null, null, null); + String response = liveImportClient.executeHttpGetRequest(timeout, ENDPOINT_SEARCH_SCOPUS,requestParams); + + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(response)); + Element root = document.getRootElement(); + + List namespaces = Arrays.asList( + Namespace.getNamespace("opensearch", "http://a9.com/-/spec/opensearch/1.1/")); + XPathExpression xpath = XPathFactory.instance() + .compile("opensearch:totalResults", Filters.element(), null, namespaces); + + Element count = xpath.evaluateFirst(root); + try { + return Integer.parseInt(count.getText()); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + } + + private class FindByIdCallable implements Callable> { + + private String eid; + + private FindByIdCallable(String eid) { + this.eid = eid; + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + String queryString = "EID(" + eid.replace("!", "/") + ")"; + if (StringUtils.isNotBlank(apiKey)) { + Map requestParams = getRequestParameters(queryString, viewMode, null, null); + String response = liveImportClient.executeHttpGetRequest(timeout, ENDPOINT_SEARCH_SCOPUS,requestParams); + List elements = splitToRecords(response); + for (Element record : elements) { + results.add(transformSourceRecords(record)); + } + } + return results; + } + } + + /** + * This class implements a callable to get the items based on query parameters + */ + private class FindByQueryCallable implements Callable> { + + private String title; + private String author; + private Integer year; + private Integer start; + private Integer count; + + private FindByQueryCallable(Query query) { + this.title = query.getParameterAsClass("title", String.class); + this.year = query.getParameterAsClass("year", Integer.class); + this.author = query.getParameterAsClass("author", String.class); + this.start = query.getParameterAsClass("start", Integer.class) != null ? + query.getParameterAsClass("start", Integer.class) : 0; + this.count = query.getParameterAsClass("count", Integer.class) != null ? + query.getParameterAsClass("count", Integer.class) : 20; + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + String queryString = ""; + StringBuffer query = new StringBuffer(); + if (StringUtils.isNotBlank(title)) { + query.append("title(").append(title).append(""); + } + if (StringUtils.isNotBlank(author)) { + // [FAU] + if (query.length() > 0) { + query.append(" AND "); + } + query.append("AUTH(").append(author).append(")"); + } + if (year != -1) { + // [DP] + if (query.length() > 0) { + query.append(" AND "); + } + query.append("PUBYEAR IS ").append(year); + } + queryString = query.toString(); + + if (apiKey != null && !apiKey.equals("")) { + Map requestParams = getRequestParameters(queryString, viewMode, start, count); + String response = liveImportClient.executeHttpGetRequest(timeout, ENDPOINT_SEARCH_SCOPUS,requestParams); + List elements = splitToRecords(response); + for (Element record : elements) { + results.add(transformSourceRecords(record)); + } + } + return results; + } + } + + private class SearchByQueryCallable implements Callable> { + private Query query; + + + private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { + query = new Query(); + query.addParameter("query", queryString); + query.addParameter("start", start); + query.addParameter("count", maxResult); + } + + private SearchByQueryCallable(Query query) { + this.query = query; + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + String queryString = query.getParameterAsClass("query", String.class); + Integer start = query.getParameterAsClass("start", Integer.class); + Integer count = query.getParameterAsClass("count", Integer.class); + if (StringUtils.isNotBlank(apiKey)) { + Map requestParams = getRequestParameters(queryString, viewMode, start, count); + String response = liveImportClient.executeHttpGetRequest(timeout, ENDPOINT_SEARCH_SCOPUS,requestParams); + List elements = splitToRecords(response); + for (Element record : elements) { + results.add(transformSourceRecords(record)); + } + } + return results; + } + } + + private Map getRequestParameters(String query, String viewMode, Integer start, Integer count) { + Map params = new HashMap(); + params.put("httpAccept", "application/xml"); + params.put("apiKey", apiKey); + params.put("query", query); + + if (StringUtils.isNotBlank(instKey)) { + params.put("insttoken", instKey); + } + if (StringUtils.isNotBlank(viewMode)) { + params.put("view", viewMode); + } + + params.put("start", (Objects.nonNull(start) ? start + "" : "0")); + params.put("count", (Objects.nonNull(count) ? count + "" : "20")); + return params; + } + + private List splitToRecords(String recordsSrc) { + try { + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(recordsSrc)); + Element root = document.getRootElement(); + List records = root.getChildren("entry",Namespace.getNamespace("http://www.w3.org/2005/Atom")); + return records; + } catch (JDOMException | IOException e) { + return new ArrayList(); + } + } + + public String getViewMode() { + return viewMode; + } + + public void setViewMode(String viewMode) { + this.viewMode = viewMode; + } + + public String getApiKey() { + return apiKey; + } + + public String getInstKey() { + return instKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public void setInstKey(String instKey) { + this.instKey = instKey; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java b/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.java new file mode 100644 index 0000000000..3b15a421b8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/DoiCheck.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.importer.external.service; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility class that provides methods to check if a given string is a DOI and exists on CrossRef services + * + * @author Corrado Lombardi (corrado.lombardi at 4science.it) + */ +public class DoiCheck { + + private static final List DOI_PREFIXES = Arrays.asList("http://dx.doi.org/", "https://dx.doi.org/"); + + private static final Pattern PATTERN = Pattern.compile("10.\\d{4,9}/[-._;()/:A-Z0-9]+" + + "|10.1002/[^\\s]+" + + "|10.\\d{4}/\\d+-\\d+X?(\\d+)" + + "\\d+<[\\d\\w]+:[\\d\\w]*>\\d+.\\d+.\\w+;\\d" + + "|10.1021/\\w\\w\\d++" + + "|10.1207/[\\w\\d]+\\&\\d+_\\d+", + Pattern.CASE_INSENSITIVE); + + private DoiCheck() {} + + public static boolean isDoi(final String value) { + Matcher m = PATTERN.matcher(purgeDoiValue(value)); + return m.matches(); + } + + public static String purgeDoiValue(final String query) { + String value = query.replaceAll(",", ""); + for (final String prefix : DOI_PREFIXES) { + value = value.replaceAll(prefix, ""); + } + return value.trim(); + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/submit/lookup/MapConverterModifier.java b/dspace-api/src/main/java/org/dspace/submit/lookup/MapConverterModifier.java new file mode 100644 index 0000000000..ea02661ca2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/submit/lookup/MapConverterModifier.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.submit.lookup; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.dspace.services.ConfigurationService; + +/** + * @author Andrea Bollini + * @author Kostas Stamatis + * @author Luigi Andrea Pascarelli + * @author Panagiotis Koutsourakis + */ +public class MapConverterModifier { + + protected String mappingFile; // The properties absolute filename + + protected String converterNameFile; // The properties filename + + protected ConfigurationService configurationService; + + protected Map mapping; + + protected String defaultValue = ""; + + protected List fieldKeys; + + protected Map regexConfig = new HashMap(); + + public final String REGEX_PREFIX = "regex."; + + public void init() { + this.mappingFile = configurationService.getProperty( + "dspace.dir") + File.separator + "config" + File.separator + "crosswalks" + File.separator + + converterNameFile; + + this.mapping = new HashMap(); + + FileInputStream fis = null; + try { + fis = new FileInputStream(new File(mappingFile)); + Properties mapConfig = new Properties(); + mapConfig.load(fis); + fis.close(); + for (Object key : mapConfig.keySet()) { + String keyS = (String) key; + if (keyS.startsWith(REGEX_PREFIX)) { + String regex = keyS.substring(REGEX_PREFIX.length()); + String regReplace = mapping.get(keyS); + if (regReplace == null) { + regReplace = ""; + } else if (regReplace.equalsIgnoreCase("@ident@")) { + regReplace = "$0"; + } + regexConfig.put(regex, regReplace); + } + if (mapConfig.getProperty(keyS) != null) { + mapping.put(keyS, mapConfig.getProperty(keyS)); + } else { + mapping.put(keyS, ""); + } + } + } catch (Exception e) { + throw new IllegalArgumentException("", e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException ioe) { + // ... + } + } + } + for (String keyS : mapping.keySet()) { + if (keyS.startsWith(REGEX_PREFIX)) { + String regex = keyS.substring(REGEX_PREFIX.length()); + String regReplace = mapping.get(keyS); + if (regReplace == null) { + regReplace = ""; + } else if (regReplace.equalsIgnoreCase("@ident@")) { + regReplace = "$0"; + } + regexConfig.put(regex, regReplace); + } + } + } + + public void setFieldKeys(List fieldKeys) { + this.fieldKeys = fieldKeys; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public void setConverterNameFile(String converterNameFile) { + this.converterNameFile = converterNameFile; + } + + public void setConfigurationService(ConfigurationService configurationService) { + this.configurationService = configurationService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/util/SimpleMapConverter.java b/dspace-api/src/main/java/org/dspace/util/SimpleMapConverter.java new file mode 100644 index 0000000000..5648879b77 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/util/SimpleMapConverter.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.util; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.submit.lookup.MapConverterModifier; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class SimpleMapConverter extends MapConverterModifier { + + public String getValue(String key) { + boolean matchEmpty = false; + String stringValue = key; + + String tmp = ""; + if (mapping.containsKey(stringValue)) { + tmp = mapping.get(stringValue); + } else { + tmp = defaultValue; + for (String regex : regexConfig.keySet()) { + if (stringValue != null && stringValue.matches(regex)) { + tmp = stringValue.replaceAll(regex, regexConfig.get(regex)); + if (StringUtils.isBlank(tmp)) { + matchEmpty = true; + } + } + } + } + + if ("@@ident@@".equals(tmp)) { + return stringValue; + } else if (StringUtils.isNotBlank(tmp) || (StringUtils.isBlank(tmp) && matchEmpty)) { + return tmp; + } + return stringValue; + } + +} \ No newline at end of file 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 5e69ee9c42..83b5439ae3 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 @@ -115,6 +115,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 ac163d3581..47e7519615 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 @@ -6,6 +6,8 @@ + + @@ -91,6 +93,17 @@ + + + + + + + Publication + + + + diff --git a/dspace/config/crosswalks/mapConverter-openAccesFlag.properties b/dspace/config/crosswalks/mapConverter-openAccesFlag.properties new file mode 100644 index 0000000000..9703a0f388 --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-openAccesFlag.properties @@ -0,0 +1 @@ +true=open access \ No newline at end of file diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index d5c28da096..8b985533c2 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1586,6 +1586,19 @@ request.item.helpdesk.override = false # ${dspace.dir}/config/ directory. module_dir = modules +#################################################################### +#------------------- SCOPUS SERVICES ------------------------------# +#------------------------------------------------------------------# + +scopus.apiKey = +# leave empty if you don't need to use an institutional token +scopus.instToken = +#The view mode to be used for the scopus search endpoint. +#For more details see https://dev.elsevier.com/documentation/ScopusSearchAPI.wadl +scopus.search-api.viewMode = COMPLETE + +#################################################################### + # Load default module configs # ---------------------------- # To exclude a module configuration, simply comment out its "include" statement. diff --git a/dspace/config/spring/api/crosswalks.xml b/dspace/config/spring/api/crosswalks.xml new file mode 100644 index 0000000000..61b43cad27 --- /dev/null +++ b/dspace/config/spring/api/crosswalks.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 7f1295f839..af8af57835 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -94,5 +94,16 @@ + + + + + + + Publication + + + + diff --git a/dspace/config/spring/api/scopus-integration.xml b/dspace/config/spring/api/scopus-integration.xml new file mode 100644 index 0000000000..ba59928ea1 --- /dev/null +++ b/dspace/config/spring/api/scopus-integration.xml @@ -0,0 +1,338 @@ + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 9bca9e21e184b1a564184c7f0ecb14e28d7b0863 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 4 Apr 2022 16:18:13 +0200 Subject: [PATCH 0806/1254] [CST-5303] added tests for scopus live import service --- .../assetstore/scopus-empty-resp.xml | 11 + .../dspaceFolder/assetstore/scopus-ex.xml | 178 ++++++++++++ .../src/test/resources/test-config.properties | 2 + .../ScopusImportMetadataSourceServiceIT.java | 255 ++++++++++++++++++ 4 files changed, 446 insertions(+) create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/scopus-empty-resp.xml create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/scopus-ex.xml create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-empty-resp.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-empty-resp.xml new file mode 100644 index 0000000000..b2b4264b5c --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-empty-resp.xml @@ -0,0 +1,11 @@ + + + 0 + 0 + 0 + + + + Result set was empty + + \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-ex.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-ex.xml new file mode 100644 index 0000000000..84fd7e71e2 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-ex.xml @@ -0,0 +1,178 @@ + + + 2 + 0 + 2 + + + + + + + + + + + https://api.elsevier.com/content/abstract/scopus_id/85124241875 + SCOPUS_ID:85124241875 + 2-s2.0-85124241875 + Hardy potential versus lower order terms in Dirichlet problems: regularizing effects<sup>†</sup> + Arcoya D. + Mathematics In Engineering + 26403501 + 5 + 1 + + 2023-01-01 + 2023 + 10.3934/mine.2023004 + In this paper, dedicated to Ireneo Peral, we study the regularizing effect of some lower order terms in Dirichlet problems despite the presence of Hardy potentials in the right hand side. + 0 + + https://api.elsevier.com/content/affiliation/affiliation_id/60032350 + 60032350 + Sapienza Università di Roma + Rome + Italy + + + https://api.elsevier.com/content/affiliation/affiliation_id/60027844 + 60027844 + Universidad de Granada + Granada + Spain + + Journal + ar + Article + 3 + + https://api.elsevier.com/content/author/author_id/6602330574 + 6602330574 + Arcoya D. + Arcoya + David + D. + 60027844 + + + https://api.elsevier.com/content/author/author_id/7003612261 + 7003612261 + Boccardo L. + Boccardo + Lucio + L. + 60032350 + + + https://api.elsevier.com/content/author/author_id/6602595438 + 6602595438 + Orsina L. + Orsina + Luigi + L. + 60032350 + + Hardy potentials | Laplace equation | Summability of solutions + 21101039848 + PGC2018-096422-B-I00 + Junta de Andalucía + 1 + true + + all + publisherfullgold + repository + repositoryvor + + + All Open Access + Gold + Green + + + + + + + + https://api.elsevier.com/content/abstract/scopus_id/85124226483 + SCOPUS_ID:85124226483 + 2-s2.0-85124226483 + Large deviations for a binary collision model: energy evaporation<sup>†</sup> + Basile G. + Mathematics In Engineering + 26403501 + 5 + 1 + + 2023-01-01 + 2023 + 10.3934/mine.2023001 + We analyze the large deviations for a discrete energy Kac-like walk. In particular, we exhibit a path, with probability exponentially small in the number of particles, that looses energy. + 0 + + https://api.elsevier.com/content/affiliation/affiliation_id/60032350 + 60032350 + Sapienza Università di Roma + Rome + Italy + + Journal + ar + Article + 4 + + https://api.elsevier.com/content/author/author_id/55613229065 + 55613229065 + Basile G. + Basile + Giada + G. + 60032350 + + + https://api.elsevier.com/content/author/author_id/55893665100 + 55893665100 + Benedetto D. + Benedetto + Dario + D. + 60032350 + + + https://api.elsevier.com/content/author/author_id/7004588675 + 7004588675 + Caglioti E. + Caglioti + Emanuele + E. + 60032350 + + + https://api.elsevier.com/content/author/author_id/7005555198 + 7005555198 + Bertini L. + Bertini + Lorenzo + L. + 60032350 + + Boltzmann equation | Discrete energy model | Kac model | Large deviations | Violation of energy conservation + 21101039848 + undefined + 1 + true + + all + publisherfullgold + repository + repositoryam + + + All Open Access + Gold + Green + + + \ No newline at end of file diff --git a/dspace-api/src/test/resources/test-config.properties b/dspace-api/src/test/resources/test-config.properties index 66a29ab9a0..ca473dcf86 100644 --- a/dspace-api/src/test/resources/test-config.properties +++ b/dspace-api/src/test/resources/test-config.properties @@ -13,3 +13,5 @@ test.folder = ./target/testing/ test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf test.exportcsv = ./target/testing/dspace/assetstore/test.csv test.importcsv = ./target/testing/dspace/assetstore/testImport.csv +test.scopus = ./target/testing/dspace/assetstore/scopus-ex.xml +test.scopus-empty = ./target/testing/dspace/assetstore/scopus-empty-resp.xml 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 new file mode 100644 index 0000000000..a3ec481a21 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java @@ -0,0 +1,255 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.FileInputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.tools.ant.filters.StringInputStream; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.scopus.service.LiveImportClientImpl; +import org.dspace.importer.external.scopus.service.ScopusImportMetadataSourceServiceImpl; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link ScopusImportMetadataSourceServiceImpl} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class ScopusImportMetadataSourceServiceIT extends AbstractControllerIntegrationTest { + + @Autowired + private ScopusImportMetadataSourceServiceImpl scopusServiceImpl; + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Test + public void scopusImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + String originApiKey = scopusServiceImpl.getApiKey(); + if (StringUtils.isBlank(originApiKey)) { + scopusServiceImpl.setApiKey("testApiKey"); + } + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.scopus").toString(); + try (FileInputStream file = new FileInputStream(path)) { + String xmlMetricsExample = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + + CloseableHttpResponse response = mockResponse(xmlMetricsExample, 200, "OK"); + + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection collection2match = getRecords(); + Collection recordsImported = scopusServiceImpl.getRecords("test query", 0, 2); + assertEquals(2, recordsImported.size()); + assertTrue(matchRecords(recordsImported, collection2match)); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + scopusServiceImpl.setApiKey(originApiKey); + } + } + + @Test + public void scopusImportMetadataGetRecordsCountTest() throws Exception { + context.turnOffAuthorisationSystem(); + String originApiKey = scopusServiceImpl.getApiKey(); + if (StringUtils.isBlank(originApiKey)) { + scopusServiceImpl.setApiKey("testApiKey"); + } + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.scopus").toString(); + try (FileInputStream file = new FileInputStream(path)) { + String xmlMetricsExample = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + + CloseableHttpResponse response = mockResponse(xmlMetricsExample, 200, "OK"); + + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + int tot = scopusServiceImpl.getRecordsCount("test query"); + assertEquals(2, tot); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + scopusServiceImpl.setApiKey(originApiKey); + } + } + + @Test + public void scopusImportMetadataGetRecordsEmptyResponceTest() throws Exception { + context.turnOffAuthorisationSystem(); + String originApiKey = scopusServiceImpl.getApiKey(); + if (StringUtils.isBlank(originApiKey)) { + scopusServiceImpl.setApiKey("testApiKey"); + } + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.scopus-empty").toString(); + try (FileInputStream file = new FileInputStream(path)) { + String xmlMetricsExample = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + + CloseableHttpResponse response = mockResponse(xmlMetricsExample, 200, "OK"); + + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection recordsImported = scopusServiceImpl.getRecords("test query", 0, 20); + ImportRecord importedRecord = recordsImported.iterator().next(); + assertTrue(importedRecord.getValueList().isEmpty()); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + scopusServiceImpl.setApiKey(originApiKey); + } + } + + private boolean matchRecords(Collection recordsImported, Collection records2match) { + ImportRecord firstImported = recordsImported.iterator().next(); + ImportRecord secondImported = recordsImported.iterator().next(); + ImportRecord first2match = recordsImported.iterator().next(); + ImportRecord second2match = recordsImported.iterator().next(); + boolean checkFirstRecord = firstImported.getValueList().containsAll(first2match.getValueList()); + boolean checkSecondRecord = secondImported.getValueList().containsAll(second2match.getValueList()); + return checkFirstRecord && checkSecondRecord; + } + + private Collection getRecords() { + Collection records = new LinkedList(); + List metadatums = new ArrayList(); + //define first record + MetadatumDTO title = createMetadatumDTO("dc","title", null, + "Hardy potential versus lower order terms in Dirichlet problems: regularizing effects"); + MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023004"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); + MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Journal"); + MetadatumDTO citationVolume = createMetadatumDTO("oaire", "citation", "volume", "5"); + MetadatumDTO citationIssue = createMetadatumDTO("oaire", "citation", "issue", "1"); + MetadatumDTO scopusId = createMetadatumDTO("dc", "identifier", "scopus", "2-s2.0-85124241875"); + MetadatumDTO funding = createMetadatumDTO("dc", "relation", "funding", "Junta de Andalucía"); + MetadatumDTO grantno = createMetadatumDTO("dc", "relation", "grantno", "PGC2018-096422-B-I00"); + MetadatumDTO subject = createMetadatumDTO("dc", "subject", null, + "Hardy potentials | Laplace equation | Summability of solutions"); + MetadatumDTO rights = createMetadatumDTO("dc", "rights", null, "open access"); + MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", + "Mathematics In Engineering"); + metadatums.add(title); + metadatums.add(doi); + metadatums.add(date); + metadatums.add(type); + metadatums.add(citationVolume); + metadatums.add(citationIssue); + metadatums.add(scopusId); + metadatums.add(funding); + metadatums.add(grantno); + metadatums.add(subject); + metadatums.add(rights); + metadatums.add(ispartof); + ImportRecord firstrRecord = new ImportRecord(metadatums); + //define second record + MetadatumDTO title2 = createMetadatumDTO("dc","title", null, + "Large deviations for a binary collision model: energy evaporation"); + MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023001"); + MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); + MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "Journal"); + MetadatumDTO citationVolume2 = createMetadatumDTO("oaire", "citation", "volume", "5"); + MetadatumDTO citationIssue2 = createMetadatumDTO("oaire", "citation", "issue", "1"); + MetadatumDTO scopusId2 = createMetadatumDTO("dc", "identifier", "scopus", "2-s2.0-85124226483"); + MetadatumDTO grantno2 = createMetadatumDTO("dc", "relation", "grantno", "undefined"); + MetadatumDTO subject2 = createMetadatumDTO("dc", "subject", null, + "Boltzmann equation | Discrete energy model | Kac model | Large deviations | Violation of energy conservation"); + MetadatumDTO rights2 = createMetadatumDTO("dc", "rights", null, "open access"); + MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", + "Mathematics In Engineering"); + metadatums.add(title2); + metadatums.add(doi2); + metadatums.add(date2); + metadatums.add(type2); + metadatums.add(citationVolume2); + metadatums.add(citationIssue2); + metadatums.add(scopusId2); + metadatums.add(grantno2); + metadatums.add(subject2); + metadatums.add(rights2); + metadatums.add(ispartof2); + ImportRecord secondRecord = new ImportRecord(metadatums); + records.add(firstrRecord); + records.add(secondRecord); + return records; + } + + private MetadatumDTO createMetadatumDTO(String schema, String element, String qualifier, String value) { + MetadatumDTO metadatumDTO = new MetadatumDTO(); + metadatumDTO.setSchema(schema); + metadatumDTO.setElement(element); + metadatumDTO.setQualifier(qualifier); + metadatumDTO.setValue(value); + return metadatumDTO; + } + + private CloseableHttpResponse mockResponse(String xmlExample, int statusCode, String reason) + throws UnsupportedEncodingException { + BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); + basicHttpEntity.setChunked(true); + basicHttpEntity.setContent(new StringInputStream(xmlExample)); + + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(statusLine(statusCode, reason)); + when(response.getEntity()).thenReturn(basicHttpEntity); + return response; + } + + private StatusLine statusLine(int statusCode, String reason) { + return new StatusLine() { + @Override + public ProtocolVersion getProtocolVersion() { + return new ProtocolVersion("http", 1, 1); + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getReasonPhrase() { + return reason; + } + }; + } + +} \ No newline at end of file From bebe3d7bfe97ec5f27b7dec694337b082a272a1d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 29 Mar 2022 17:02:46 +0200 Subject: [PATCH 0807/1254] [CST-5303] fix tests --- dspace-server-webapp/src/test/resources/test-config.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-server-webapp/src/test/resources/test-config.properties b/dspace-server-webapp/src/test/resources/test-config.properties index 3af96b20fc..8d49daaddf 100644 --- a/dspace-server-webapp/src/test/resources/test-config.properties +++ b/dspace-server-webapp/src/test/resources/test-config.properties @@ -14,3 +14,5 @@ test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf #Path for a test Taskfile for the curate script test.curateTaskFile = ./target/testing/dspace/assetstore/curate.txt +test.scopus = ./target/testing/dspace/assetstore/scopus-ex.xml +test.scopus-empty = ./target/testing/dspace/assetstore/scopus-empty-resp.xml \ No newline at end of file From 55faacb168d32a34082dd8a59fe700b7b287ec5b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 30 Mar 2022 21:33:32 +0200 Subject: [PATCH 0808/1254] [CST-5303] porting pubmed live import --- .../SimpleXpathMetadatumContributor.java | 2 +- .../PubmedEuropeFieldMapping.java | 37 +++ ...PubmedEuropeMetadataSourceServiceImpl.java | 310 ++++++++++++++++++ .../spring-dspace-addon-import-services.xml | 12 +- .../spring/api/pubmedeurope-integration.xml | 184 +++++++++++ 5 files changed, 543 insertions(+), 2 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java create mode 100644 dspace/config/spring/api/pubmedeurope-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java index 10a4ddd4d8..0fe6e30946 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumContributor.java @@ -47,7 +47,7 @@ public class SimpleXpathMetadatumContributor implements MetadataContributor> metadataFieldMapping; + protected MetadataFieldMapping> metadataFieldMapping; /** * Return metadataFieldMapping diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeFieldMapping.java new file mode 100644 index 0000000000..a9495c3cb7 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeFieldMapping.java @@ -0,0 +1,37 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.pubmedeurope; + +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 ArXiv metadatum fields on the DSpace metadatum fields + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ +public class PubmedEuropeFieldMapping 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 = "pubmedEuropeMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} 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 new file mode 100644 index 0000000000..a1fe1159b4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java @@ -0,0 +1,310 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.pubmedeurope; + +import java.io.IOException; +import java.io.StringReader; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; +import javax.el.MethodNotFoundException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpException; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.utils.URIBuilder; +import org.apache.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.scopus.service.LiveImportClient; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.components.QuerySource; +import org.jaxen.JaxenException; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements a data source for querying PubmedEurope + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class PubmedEuropeMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + + private static final Logger log = Logger.getLogger(PubmedEuropeMetadataSourceServiceImpl.class); + + private static final String ENDPOINT_SEARCH = "https://www.ebi.ac.uk/europepmc/webservices/rest/search"; + + @Autowired + private LiveImportClient liveImportClient; + + @Override + public String getImportSource() { + return "pubmedeu"; + } + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(id)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query, count, start)); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(query)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + return retry(new FindMatchingRecordCallable(query)); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for CrossRef"); + } + + @Override + public void init() throws Exception {} + + public List getByPubmedEuropeID(String pubmedID, Integer start, Integer count) + throws IOException, HttpException { + String query = "(EXT_ID:" + pubmedID + ")"; + return search(query.toString(), count, start); + } + + 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 { + Integer count = query.getParameterAsClass("count", Integer.class); + Integer start = query.getParameterAsClass("start", Integer.class); + String queryString = query.getParameterAsClass("query", String.class); + return search(queryString, count, start); + + } + } + + 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 { + return getByPubmedEuropeID(query.getParameterAsClass("id", String.class), 1 ,0); + } + } + + public class FindMatchingRecordCallable implements Callable> { + + private Query query; + + private FindMatchingRecordCallable(Query q) { + query = q; + } + + @Override + public List call() throws Exception { + String title = query.getParameterAsClass("title", String.class); + String author = query.getParameterAsClass("author", String.class); + Integer year = query.getParameterAsClass("year", Integer.class); + Integer maxResult = query.getParameterAsClass("maxResult", Integer.class); + Integer start = query.getParameterAsClass("start", Integer.class); + return search(title, author, year, maxResult, start); + } + + } + + 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 { + try { + return count(query.getParameterAsClass("query", String.class)); + } catch (Exception e) { + throw new RuntimeException(); + } + } + } + + public Integer count(String query) throws URISyntaxException, ClientProtocolException, IOException, JaxenException { + try { + String response = liveImportClient.executeHttpGetRequest(1000, buildURI(1, query), + new HashMap()); + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(response)); + Element root = document.getRootElement(); + Element element = root.getChild("hitCount"); + return Integer.parseInt(element.getValue()); + } catch (JDOMException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + } + + public List search(String title, String author, int year, int count, int start) + throws HttpException, IOException { + StringBuffer query = new StringBuffer(); + query.append("("); + if (StringUtils.isNotBlank(title)) { + query.append("TITLE:").append(title); + query.append(")"); + } + if (StringUtils.isNotBlank(author)) { + String splitRegex = "(\\s*,\\s+|\\s*;\\s+|\\s*;+|\\s*,+|\\s+)"; + String[] authors = author.split(splitRegex); + // [FAU] + if (query.length() > 0) { + query.append(" AND "); + } + query.append("("); + int x = 0; + for (String auth : authors) { + x++; + query.append("AUTH:\"").append(auth).append("\""); + if (x < authors.length) { + query.append(" AND "); + } + } + query.append(")"); + } + if (year != -1) { + // [DP] + if (query.length() > 0) { + query.append(" AND "); + } + query.append("( PUB_YEAR:").append(year).append(")"); + } + query.append(")"); + return search(query.toString(), count, start); + } + + public List search(String query, Integer count, Integer start) throws IOException, HttpException { + List results = new ArrayList<>(); + try { + URIBuilder uriBuilder = new URIBuilder("https://www.ebi.ac.uk/europepmc/webservices/rest/search"); + uriBuilder.addParameter("format", "xml"); + uriBuilder.addParameter("resulttype", "core"); + uriBuilder.addParameter("pageSize", String.valueOf(count)); + uriBuilder.addParameter("query", query); + boolean lastPage = false; + int skipped = 0; + while (!lastPage || results.size() < count) { + String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(response)); + XPathFactory xpfac = XPathFactory.instance(); + XPathExpression xPath = xpfac.compile("//responseWrapper/resultList/result",Filters.element()); + List records = xPath.evaluate(document); + if (records.size() > 0) { + for (Element item : records) { + if (start > skipped) { + skipped++; + } else { + results.add(transformSourceRecords(item)); + } + if (results.size() == count) { + break; + } + } + } else { + lastPage = true; + break; + } + Element root = document.getRootElement(); + Element nextCursorMark = root.getChild("nextCursorMark"); + String cursorMark = Objects.nonNull(nextCursorMark) ? nextCursorMark.getValue() : StringUtils.EMPTY; + if (!"*".equals(cursorMark)) { + uriBuilder.setParameter("cursorMar", cursorMark); + } else { + lastPage = true; + } + } + } catch (URISyntaxException | JDOMException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + return results; + } + + private String buildURI(Integer pageSize, String query) throws URISyntaxException { + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_SEARCH); + uriBuilder.addParameter("format", "xml"); + uriBuilder.addParameter("resulttype", "core"); + uriBuilder.addParameter("pageSize", String.valueOf(pageSize)); + uriBuilder.addParameter("query", query); + return uriBuilder.toString(); + } + +} \ No newline at end of file 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 83b5439ae3..c9e901d89f 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 @@ -121,9 +121,19 @@ - + + + + + + + + + + + diff --git a/dspace/config/spring/api/pubmedeurope-integration.xml b/dspace/config/spring/api/pubmedeurope-integration.xml new file mode 100644 index 0000000000..3cd8e49082 --- /dev/null +++ b/dspace/config/spring/api/pubmedeurope-integration.xml @@ -0,0 +1,184 @@ + + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From d2f62939f8ce4c7bcd56bdca38119ca32ca6cd24 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 30 Mar 2022 21:36:37 +0200 Subject: [PATCH 0809/1254] [CST-5303] added test for pubmedeurope --- .../assetstore/pubmedeurope-empty.xml | 14 + .../assetstore/pubmedeurope-test.xml | 378 ++++++++++++++++++ .../PubmedEuropeMetadataSourceServiceIT.java | 248 ++++++++++++ .../src/test/resources/test-config.properties | 4 +- .../config/spring/api/external-services.xml | 11 + 5 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/pubmedeurope-empty.xml create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/pubmedeurope-test.xml create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedEuropeMetadataSourceServiceIT.java diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/pubmedeurope-empty.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/pubmedeurope-empty.xml new file mode 100644 index 0000000000..2c432eb832 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/pubmedeurope-empty.xml @@ -0,0 +1,14 @@ + + + 6.7 + 0 + + chortkiv&cursorMar + core + * + 25 + + false + + + \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/pubmedeurope-test.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/pubmedeurope-test.xml new file mode 100644 index 0000000000..e1c4dfab4c --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/pubmedeurope-test.xml @@ -0,0 +1,378 @@ + + + 6.7 + 3 + + chortkiv + core + * + 25 + + false + + + + 21733903 + MED + 21733903 + PMC3234553 + + PMC3234553 + + 10.1098/rspb.2011.0943 + First record of preserved soft parts in a Palaeozoic podocopid (Metacopina) ostracod, Cytherellina submagna: phylogenetic implications. + Olempska E, Horne DJ, Szaniawski H. + + + Olempska E + E + Olempska + E + 0000-0003-0310-3876 + + + Institute of Paleobiology, Polish Academy of Sciences, 00-818 Warszawa, Poland. olempska@twarda.pan.pl + + + + + Horne DJ + D J + Horne + DJ + 0000-0002-2148-437X + + + Szaniawski H + H + Szaniawski + H + + + + 0000-0002-2148-437X + 0000-0003-0310-3876 + + + 1728 + 279 + 1890685 + 2012 Feb + 2 + 2012 + 2012-02-01 + + Proceedings. Biological sciences + Proc Biol Sci + Proc Biol Sci + 101245157 + 0962-8452 + 1471-2954 + + + 2012 + 564-570 + The metacopines represent one of the oldest and most important extinct groups of ostracods, with a fossil record from the Mid-Ordovician to the Early Jurassic. Herein, we report the discovery of a representative of the group with three-dimensionally preserved soft parts. The specimen--a male of Cytherellina submagna--was found in the Early Devonian (416 Ma) of Podolia, Ukraine. A branchial plate (Bp) of the cephalic maxillula (Mx), a pair of thoracic appendages (walking legs), a presumed furca (Fu) and a copulatory organ are preserved. The material also includes phosphatized steinkerns with exceptionally preserved marginal pore canals and muscle scars. The morphology of the preserved limbs and valves of C. submagna suggests its relationship with extant Podocopida, particularly with the superfamilies Darwinuloidea and Sigillioidea, which have many similar characteristic features, including a large Bp on the Mx, the morphology of walking legs, Fu with two terminal claws, internal stop-teeth in the left valve, adductor muscle scar pattern, and a very narrow fused zone along the anterior and posterior margins. More precise determination of affinities will depend on the soft-part morphology of the cephalic segment, which has not been revealed in the present material. + Institute of Paleobiology, Polish Academy of Sciences, 00-818 Warszawa, Poland. olempska@twarda.pan.pl + ppublish + eng + Print-Electronic + + Research Support, Non-U.S. Gov't + research-article + Journal Article + + + + N + Animals + + + N + Crustacea + + + AH + anatomy & histology + Y + + + CL + classification + Y + + + + + Y + Phylogeny + + + Y + Fossils + + + N + Ukraine + + + N + Male + + + + + IM + Index Medicus + + + + + Free + F + pdf + PubMedCentral + https://www.ncbi.nlm.nih.gov/pmc/articles/pmid/21733903/pdf/?tool=EBI + + + Free + F + html + PubMedCentral + https://www.ncbi.nlm.nih.gov/pmc/articles/pmid/21733903/?tool=EBI + + + Subscription required + S + doi + DOI + https://doi.org/10.1098/rspb.2011.0943 + + + Free + F + html + Europe_PMC + https://europepmc.org/articles/PMC3234553 + + + Free + F + pdf + Europe_PMC + https://europepmc.org/articles/PMC3234553?pdf=render + + + Free + F + doi + DOI + https://doi.org/10.1098/rspb.2011.0943 + + + N + Y + Y + Y + N + N + 2 + N + Y + Y + N + N + N + N + N + N + 2012-04-19 + 2011-07-08 + 2011-07-08 + 2020-07-11 + 2021-10-20 + 2011-07-06 + 2011-07-06 + + + UA37818 + PAT + VODKA SYNEVIR + BARANOV VALENTYN VOLODYMYROVYC, DANCHAK ROMAN ADAMOVYCH, FEDORCHUK NATALIIA HRYHORIVNA, FEDOREIKO LIUBOV ROMANIVNA. + + + BARANOV VALENTYN VOLODYMYROVYC + BARANOV VALENTYN VOLODYMYROVYC + + + DANCHAK ROMAN ADAMOVYCH + DANCHAK ROMAN ADAMOVYCH + + + FEDORCHUK NATALIIA HRYHORIVNA + FEDORCHUK NATALIIA HRYHORIVNA + + + FEDOREIKO LIUBOV ROMANIVNA + FEDOREIKO LIUBOV ROMANIVNA + + + 2004 + Vodka contains aqueous-alcoholic mixture, 65.8 % sugar syrup and apple vinegar. As a result the vodka has light taste without vodka acid and light apple aroma. + CHORTKIV DISTILLARY + eng + + Patent + + + UA + C2 + + + C12G3/06 + IPC + http://www.cooperativepatentclassification.org/cpc/scheme/C/scheme-C12G.pdf + + + + UA20000042245 + 2000-04-19 + 0 + + + + UA20000042245 + 2000-04-19 + 1 + + + + + + Free + F + html + SureChembl + https://www.surechembl.org/document/UA-37818-U + + + Free + F + html + EPO + http://v3.espacenet.com/textdoc?DB=EPODOC&IDX=UA37818 + + + N + N + N + N + N + N + 0 + N + N + Y + N + N + N + N + N + N + 2004-12-15 + 2000-04-19 + 2010-11-01 + 2000-04-19 + + + UA37954 + PAT + A VODKA CHARKA + BARANOV VALENTYN VOLODYMYROVYC, DANCHAK ROMAN ADAMOVYCH, FEDORCHUK NATALIIA HRYHORIVNA, FEDOREIKO LIUBOV ROMANIVNA. + + + BARANOV VALENTYN VOLODYMYROVYC + BARANOV VALENTYN VOLODYMYROVYC + + + DANCHAK ROMAN ADAMOVYCH + DANCHAK ROMAN ADAMOVYCH + + + FEDORCHUK NATALIIA HRYHORIVNA + FEDORCHUK NATALIIA HRYHORIVNA + + + FEDOREIKO LIUBOV ROMANIVNA + FEDOREIKO LIUBOV ROMANIVNA + + + 2005 + The invention relates to food industry, and particularly to liqueur and vodka industry, to vodkas compositions. The aim of this invention is producing vodka with high organoleptic indices, and particularly soft taste without vodka bitterness and without vodka aroma, and high biological properties, by selection of necessary ingredients at required quantities. Ingredients ratio at 100 l of finished drink: Carbohydrate module ôAlkosoftö, l 0.07-0.13Citric oil, kg 0,0015-0,0025 Aqueous-alcoholic mixture as calculated per strength of 40% of volume rest. Technical result - preparation of the vodka of given composition with a strength of 40% of volume, which is transparent, colorless, has mild taste without vodka bitterness and without strong vodka aroma which will not cause alcohol withdrawal syndrome and high charge on the body. + CHORTKIV DISTILLARY + eng + + Patent + + + UA + C2 + + + C12G3/06 + IPC + http://www.cooperativepatentclassification.org/cpc/scheme/C/scheme-C12G.pdf + + + + UA20000052634 + 2000-05-10 + 0 + + + + UA20000052634 + 2000-05-10 + 1 + + + + + + Free + F + html + SureChembl + https://www.surechembl.org/document/UA-37954-U + + + Free + F + html + EPO + http://v3.espacenet.com/textdoc?DB=EPODOC&IDX=UA37954 + + + N + N + N + N + N + N + 0 + N + N + Y + N + N + N + N + N + N + 2005-02-15 + 2000-05-10 + 2010-10-18 + 2000-05-10 + + + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedEuropeMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedEuropeMetadataSourceServiceIT.java new file mode 100644 index 0000000000..78a1777370 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedEuropeMetadataSourceServiceIT.java @@ -0,0 +1,248 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.FileInputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.tools.ant.filters.StringInputStream; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.pubmedeurope.PubmedEuropeMetadataSourceServiceImpl; +import org.dspace.importer.external.scopus.service.LiveImportClientImpl; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link PubmedEuropeMetadataSourceServiceImpl} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class PubmedEuropeMetadataSourceServiceIT extends AbstractControllerIntegrationTest { + + @Autowired + private PubmedEuropeMetadataSourceServiceImpl pubmedEuropeMetadataServiceImpl; + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Test + public void pubmedEuropeImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.pubmedeurope").toString(); + String path2file = testProps.get("test.pubmedeurope-empty").toString(); + try (FileInputStream file = new FileInputStream(path)) { + try (FileInputStream file2 = new FileInputStream(path2file)) { + String xmlMetricsExample = IOUtils.toString(file, Charset.defaultCharset()); + String xmlMetricsExample2 = IOUtils.toString(file2, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + + CloseableHttpResponse response = mockResponse(xmlMetricsExample, 200, "OK"); + CloseableHttpResponse response2 = mockResponse(xmlMetricsExample2, 200, "OK"); + + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response, response2); + + context.restoreAuthSystemState(); + Collection recordsImported = pubmedEuropeMetadataServiceImpl.getRecords("test query", 0, 3); + Collection collection2match = getRecords(); + assertEquals(3, recordsImported.size()); + assertTrue(matchRecords(recordsImported, collection2match)); + } + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + private boolean matchRecords(Collection recordsImported, Collection records2match) { + ImportRecord firstImported = recordsImported.iterator().next(); + ImportRecord secondImported = recordsImported.iterator().next(); + ImportRecord first2match = recordsImported.iterator().next(); + ImportRecord second2match = recordsImported.iterator().next(); + boolean checkFirstRecord = firstImported.getValueList().containsAll(first2match.getValueList()); + boolean checkSecondRecord = secondImported.getValueList().containsAll(second2match.getValueList()); + return checkFirstRecord && checkSecondRecord; + } + + private CloseableHttpResponse mockResponse(String xmlExample, int statusCode, String reason) + throws UnsupportedEncodingException { + BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); + basicHttpEntity.setChunked(true); + basicHttpEntity.setContent(new StringInputStream(xmlExample)); + + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(statusLine(statusCode, reason)); + when(response.getEntity()).thenReturn(basicHttpEntity); + return response; + } + + private Collection getRecords() { + Collection records = new LinkedList(); + List metadatums = new ArrayList(); + //define first record + MetadatumDTO title = createMetadatumDTO("dc","title", null, + "First record of preserved soft parts in a Palaeozoic podocopid" + + " (Metacopina) ostracod, Cytherellina submagna: phylogenetic implications."); + MetadatumDTO contributor = createMetadatumDTO("dc", "contributor", "author", "Olempska E"); + MetadatumDTO contributor2 = createMetadatumDTO("dc", "contributor", "author", "Horne DJ"); + MetadatumDTO contributor3 = createMetadatumDTO("dc", "contributor", "author", "Szaniawski H"); + MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.1098/rspb.2011.0943"); + MetadatumDTO source = createMetadatumDTO("dc", "source", null, "Proceedings. Biological sciences"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2012"); + MetadatumDTO language = createMetadatumDTO("dc", "language", "iso", "eng"); + MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Research Support, Non-U.S. Gov't"); + MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "research-article"); + MetadatumDTO type3 = createMetadatumDTO("dc", "type", null, "Journal Article"); + + MetadatumDTO issn = createMetadatumDTO("dc", "identifier", "issn", "0962-8452"); + MetadatumDTO pmid = createMetadatumDTO("dc", "identifier", "pmid", "21733903"); + MetadatumDTO description = createMetadatumDTO("dc", "description", "abstract", "The metacopines represent one" + + " of the oldest and most important extinct groups of ostracods, with a fossil record from" + + " the Mid-Ordovician to the Early Jurassic. Herein, we report the discovery of a representative" + + " of the group with three-dimensionally preserved soft parts. The specimen--a male of Cytherellina" + + " submagna--was found in the Early Devonian (416 Ma) of Podolia, Ukraine. A branchial plate (Bp)" + + " of the cephalic maxillula (Mx), a pair of thoracic appendages (walking legs), a presumed furca" + + " (Fu) and a copulatory organ are preserved. The material also includes phosphatized steinkerns" + + " with exceptionally preserved marginal pore canals and muscle scars. The morphology of the" + + " preserved limbs and valves of C. submagna suggests its relationship with extant Podocopida," + + " particularly with the superfamilies Darwinuloidea and Sigillioidea, which have many similar" + + " characteristic features, including a large Bp on the Mx, the morphology of walking legs, Fu" + + " with two terminal claws, internal stop-teeth in the left valve, adductor muscle scar pattern," + + " and a very narrow fused zone along the anterior and posterior margins. More precise" + + " determination of affinities will depend on the soft-part morphology of the cephalic segment," + + " which has not been revealed in the present material."); + + metadatums.add(title); + metadatums.add(contributor); + metadatums.add(contributor2); + metadatums.add(contributor3); + metadatums.add(doi); + metadatums.add(source); + metadatums.add(date); + metadatums.add(language); + metadatums.add(type); + metadatums.add(type2); + metadatums.add(type3); + metadatums.add(issn); + metadatums.add(pmid); + metadatums.add(description); + ImportRecord firstrRecord = new ImportRecord(metadatums); + + //define second record + List metadatums2 = new ArrayList(); + MetadatumDTO title2 = createMetadatumDTO("dc","title", null, "VODKA SYNEVIR"); + MetadatumDTO contributor4 = createMetadatumDTO("dc", "contributor", "author", "BARANOV VALENTYN VOLODYMYROVYC"); + MetadatumDTO contributor5 = createMetadatumDTO("dc", "contributor", "author", "DANCHAK ROMAN ADAMOVYCH"); + MetadatumDTO contributor6 = createMetadatumDTO("dc", "contributor", "author", "FEDORCHUK NATALIIA HRYHORIVNA"); + MetadatumDTO contributor7 = createMetadatumDTO("dc", "contributor", "author", "FEDOREIKO LIUBOV ROMANIVNA"); + MetadatumDTO language2 = createMetadatumDTO("dc", "language", "iso", "eng"); + MetadatumDTO type4 = createMetadatumDTO("dc", "type", null, "Patent"); + MetadatumDTO pmid2 = createMetadatumDTO("dc", "identifier", "pmid", "UA37818"); + MetadatumDTO description2 = createMetadatumDTO("dc", "description", "abstract", + "Vodka contains aqueous-alcoholic" + + " mixture, 65.8 % sugar syrup and apple vinegar." + + " As a result the vodka has light taste without vodka acid and light apple aroma."); + metadatums2.add(title2); + metadatums2.add(contributor4); + metadatums2.add(contributor5); + metadatums2.add(contributor6); + metadatums2.add(contributor7); + metadatums2.add(language2); + metadatums2.add(type4); + metadatums2.add(pmid2); + metadatums2.add(description2); + ImportRecord secondRecord = new ImportRecord(metadatums2); + + //define second record + List metadatums3 = new ArrayList(); + MetadatumDTO title3 = createMetadatumDTO("dc","title", null, "A VODKA CHARKA"); + MetadatumDTO contributor8 = createMetadatumDTO("dc", "contributor", "author", "BARANOV VALENTYN VOLODYMYROVYC"); + MetadatumDTO contributor9 = createMetadatumDTO("dc", "contributor", "author", "HDANCHAK ROMAN ADAMOVYCH"); + MetadatumDTO contributor10 = createMetadatumDTO("dc", "contributor", "author", "FEDORCHUK NATALIIA HRYHORIVNA"); + MetadatumDTO contributor11 = createMetadatumDTO("dc", "contributor", "author", "FEDOREIKO LIUBOV ROMANIVNA"); + MetadatumDTO language3 = createMetadatumDTO("dc", "language", "iso", "eng"); + MetadatumDTO type5 = createMetadatumDTO("dc", "type", null, "Patent"); + MetadatumDTO pmid3 = createMetadatumDTO("dc", "identifier", "pmid", "UA37954"); + MetadatumDTO description3 = createMetadatumDTO("dc", "description", "abstract", "The invention relates to" + + " food industry, and particularly to liqueur and vodka industry, to vodkas compositions." + + " The aim of this invention is producing vodka with high organoleptic indices, and particularly" + + " soft taste without vodka bitterness and without vodka aroma, and high biological properties," + + " by selection of necessary ingredients at required quantities. Ingredients ratio at 100 l of" + + " finished drink: Carbohydrate module ôAlkosoftö, l 0.07-0.13Citric oil, kg 0,0015-0,0025" + + " Aqueous-alcoholic mixture as calculated per strength of 40% of volume rest." + + " Technical result - preparation of the vodka of given composition with a strength of 40% of" + + " volume, which is transparent, colorless, has mild taste without vodka bitterness and without" + + " strong vodka aroma which will not cause alcohol withdrawal syndrome and high charge on the body."); + metadatums3.add(title3); + metadatums3.add(contributor8); + metadatums3.add(contributor9); + metadatums3.add(contributor10); + metadatums3.add(contributor11); + metadatums3.add(language3); + metadatums3.add(type5); + metadatums3.add(pmid3); + metadatums3.add(description3); + ImportRecord thirdRecord = new ImportRecord(metadatums3); + + records.add(firstrRecord); + records.add(secondRecord); + records.add(thirdRecord); + return records; + } + + private MetadatumDTO createMetadatumDTO(String schema, String element, String qualifier, String value) { + MetadatumDTO metadatumDTO = new MetadatumDTO(); + metadatumDTO.setSchema(schema); + metadatumDTO.setElement(element); + metadatumDTO.setQualifier(qualifier); + metadatumDTO.setValue(value); + return metadatumDTO; + } + + private StatusLine statusLine(int statusCode, String reason) { + return new StatusLine() { + @Override + public ProtocolVersion getProtocolVersion() { + return new ProtocolVersion("http", 1, 1); + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getReasonPhrase() { + return reason; + } + }; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/test-config.properties b/dspace-server-webapp/src/test/resources/test-config.properties index 8d49daaddf..ac04d032d6 100644 --- a/dspace-server-webapp/src/test/resources/test-config.properties +++ b/dspace-server-webapp/src/test/resources/test-config.properties @@ -15,4 +15,6 @@ test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf #Path for a test Taskfile for the curate script test.curateTaskFile = ./target/testing/dspace/assetstore/curate.txt test.scopus = ./target/testing/dspace/assetstore/scopus-ex.xml -test.scopus-empty = ./target/testing/dspace/assetstore/scopus-empty-resp.xml \ No newline at end of file +test.scopus-empty = ./target/testing/dspace/assetstore/scopus-empty-resp.xml +test.pubmedeurope = ./target/testing/dspace/assetstore/pubmedeurope-test.xml +test.pubmedeurope-empty = ./target/testing/dspace/assetstore/pubmedeurope-empty.xml \ No newline at end of file diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index af8af57835..35c2381351 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -105,5 +105,16 @@ + + + + + + + Publication + + + + From 9cde585afcb718b386f3520a37cf7b113fdf26aa Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 31 Mar 2022 22:39:52 +0200 Subject: [PATCH 0810/1254] [CST-5303] porting of wos --- .../contributor/SimpleConcatContributor.java | 46 ++ .../SimpleMultiplePathContributor.java | 55 ++ ...XpathMetadatumAndAttributeContributor.java | 56 +++ .../WosAttribute2ValueContributor.java | 152 ++++++ .../contributor/WosIdentifierContributor.java | 69 +++ .../WosIdentifierRidContributor.java | 65 +++ .../external/wos/service/WOSFieldMapping.java | 37 ++ .../WOSImportMetadataSourceServiceImpl.java | 270 ++++++++++ .../dspaceFolder/assetstore/scopus-ex.xml | 2 - .../dspaceFolder/assetstore/wos-responce.xml | 469 ++++++++++++++++++ .../AbstractLiveImportIntegrationTest.java | 80 +++ .../PubmedEuropeMetadataSourceServiceIT.java | 81 +-- .../ScopusImportMetadataSourceServiceIT.java | 184 +++---- .../WOSImportMetadataSourceServiceIT.java | 171 +++++++ .../src/test/resources/test-config.properties | 3 +- dspace/config/dspace.cfg | 4 + dspace/config/spring/api/wos-integration.xml | 291 +++++++++++ 17 files changed, 1882 insertions(+), 153 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMultiplePathContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosAttribute2ValueContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosIdentifierContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosIdentifierRidContributor.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/wos-responce.xml create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/WOSImportMetadataSourceServiceIT.java create mode 100644 dspace/config/spring/api/wos-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java new file mode 100644 index 0000000000..e52f3ec3c3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleConcatContributor.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.metadatamapping.contributor; + +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; + +/** + * This contributor is able to concat multi value. + * Given a certain path, if it contains several nodes, + * the values of nodes will be concatenated into a single one + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4Science dot it) + */ +public class SimpleConcatContributor extends SimpleXpathMetadatumContributor { + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + StringBuilder text = new StringBuilder(); + for (String ns : prefixToNamespaceMapping.keySet()) { + List nodes = element.getChildren(query, Namespace.getNamespace(ns)); + for (Element el : nodes) { + if (StringUtils.isNotBlank(el.getValue())) { + text.append(element.getText()); + } + } + } + if (StringUtils.isNotBlank(text.toString())) { + values.add(metadataFieldMapping.toDCValue(field, text.toString())); + } + return values; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMultiplePathContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMultiplePathContributor.java new file mode 100644 index 0000000000..8356101b01 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleMultiplePathContributor.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.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jdom2.Element; +import org.jdom2.Namespace; + +/** + * This contributor can perform research on multi-paths + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4Science dot it) + */ +public class SimpleMultiplePathContributor extends SimpleXpathMetadatumContributor { + + private List paths; + + public SimpleMultiplePathContributor() {} + + public SimpleMultiplePathContributor(List paths) { + this.paths = paths; + } + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + for (String path : this.paths) { + for (String ns : prefixToNamespaceMapping.keySet()) { + List nodes = element.getChildren(path, Namespace.getNamespace(ns)); + for (Element el : nodes) { + values.add(metadataFieldMapping.toDCValue(field, el.getValue())); + } + } + } + return values; + } + + public List getPaths() { + return paths; + } + + public void setPaths(List paths) { + this.paths = paths; + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..122961953d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java @@ -0,0 +1,56 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Constants; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jdom2.Element; +import org.jdom2.Namespace; + +/** + * This contributor checks for each node returned for the supplied path + * if node contains supplied attribute - the value of the current node is taken, + * otherwise #PLACEHOLDER_PARENT_METADATA_VALUE# + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4Science dot it) + */ +public class SimpleXpathMetadatumAndAttributeContributor extends SimpleXpathMetadatumContributor { + + private String attribute; + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + for (String ns : prefixToNamespaceMapping.keySet()) { + List nodes = element.getChildren(query, Namespace.getNamespace(ns)); + for (Element el : nodes) { + String attributeValue = el.getAttributeValue(this.attribute); + if (StringUtils.isNotBlank(attributeValue)) { + values.add(metadataFieldMapping.toDCValue(this.field, attributeValue)); + } else { + values.add(metadataFieldMapping.toDCValue(this.field, Constants.PLACEHOLDER_PARENT_METADATA_VALUE)); + } + } + } + return values; + } + + public String getAttribute() { + return attribute; + } + + public void setAttribute(String attribute) { + this.attribute = attribute; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosAttribute2ValueContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosAttribute2ValueContributor.java new file mode 100644 index 0000000000..1c6a1f8875 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosAttribute2ValueContributor.java @@ -0,0 +1,152 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.annotation.Resource; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jaxen.JaxenException; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This contributor checks for each node returned for the given path if the node contains "this.attribute" + * and then checks if the attribute value is one of the values configured + * in the "this.attributeValue2metadata" map, if the value of the current known is taken. + * If "this.firstChild" is true, it takes the value of the child of the known. + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4Science dot it) + */ +public class WosAttribute2ValueContributor implements MetadataContributor { + + private static final Logger log = LoggerFactory.getLogger(WosAttribute2ValueContributor.class); + + private String query; + + private String attribute; + + private boolean firstChild; + + private String childName; + + private Map prefixToNamespaceMapping; + + private Map attributeValue2metadata; + + private MetadataFieldMapping> metadataFieldMapping; + + public WosAttribute2ValueContributor() {} + + public WosAttribute2ValueContributor(String query, + Map prefixToNamespaceMapping, + Map attributeValue2metadata) { + this.query = query; + this.prefixToNamespaceMapping = prefixToNamespaceMapping; + this.attributeValue2metadata = attributeValue2metadata; + } + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + try { + for (String ns : prefixToNamespaceMapping.keySet()) { + List nodes = element.getChildren(query, Namespace.getNamespace(ns)); + for (Element el : nodes) { + String attributeValue = el.getAttributeValue(this.attribute); + setField(attributeValue, element, values); + } + } + return values; + } catch (JaxenException e) { + log.warn(query, e); + throw new RuntimeException(e); + } + } + + private void setField(String attributeValue, Element el, List values) throws JaxenException { + for (String id : attributeValue2metadata.keySet()) { + if (StringUtils.equals(id, attributeValue)) { + if (this.firstChild) { + String value = ""; + //el.getFirstChildWithName(new QName(this.childName)).getText(); + values.add(metadataFieldMapping.toDCValue(attributeValue2metadata.get(id), value)); + } else { + values.add(metadataFieldMapping.toDCValue(attributeValue2metadata.get(id), el.getText())); + } + } + } + } + + public MetadataFieldMapping> getMetadataFieldMapping() { + return metadataFieldMapping; + } + + public void setMetadataFieldMapping( + MetadataFieldMapping> metadataFieldMapping) { + this.metadataFieldMapping = metadataFieldMapping; + } + + @Resource(name = "isiFullprefixMapping") + public void setPrefixToNamespaceMapping(Map prefixToNamespaceMapping) { + this.prefixToNamespaceMapping = prefixToNamespaceMapping; + } + + public Map getPrefixToNamespaceMapping() { + return prefixToNamespaceMapping; + } + + public String getAttribute() { + return attribute; + } + + public void setAttribute(String attribute) { + this.attribute = attribute; + } + + public Map getAttributeValue2metadata() { + return attributeValue2metadata; + } + + public void setAttributeValue2metadata(Map attributeValue2metadata) { + this.attributeValue2metadata = attributeValue2metadata; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public boolean isFirstChild() { + return firstChild; + } + + public void setFirstChild(boolean firstChild) { + this.firstChild = firstChild; + } + + public String getChildName() { + return childName; + } + + public void setChildName(String childName) { + this.childName = childName; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosIdentifierContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosIdentifierContributor.java new file mode 100644 index 0000000000..a5da4bf028 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosIdentifierContributor.java @@ -0,0 +1,69 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.metadatamapping.contributor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; +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; + +/** + * This contributor can retrieve the identifiers + * configured in "this.identifire2field" from the WOS response. + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4Science dot it) + */ +public class WosIdentifierContributor extends SimpleXpathMetadatumContributor { + + protected Map identifire2field; + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + List namespaces = new ArrayList<>(); + for (String ns : prefixToNamespaceMapping.keySet()) { + namespaces.add(Namespace.getNamespace(prefixToNamespaceMapping.get(ns), ns)); + } + XPathExpression xpath = + XPathFactory.instance().compile(query, Filters.element(), null, namespaces); + + List nodes = xpath.evaluate(element); + for (Element el : nodes) { + String type = el.getAttributeValue("type"); + setIdentyfire(type, el, values); + } + return values; + } + + private void setIdentyfire(String type, Element el, List values) { + for (String id : identifire2field.keySet()) { + if (StringUtils.equals(id, type)) { + String value = el.getAttributeValue("value"); + values.add(metadataFieldMapping.toDCValue(identifire2field.get(id), value)); + } + } + } + + public Map getIdentifire2field() { + return identifire2field; + } + + public void setIdentifire2field(Map identifire2field) { + this.identifire2field = identifire2field; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosIdentifierRidContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosIdentifierRidContributor.java new file mode 100644 index 0000000000..7ccefeee54 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/WosIdentifierRidContributor.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.importer.external.metadatamapping.contributor; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Constants; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jaxen.JaxenException; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4Science dot it) + */ +public class WosIdentifierRidContributor extends SimpleXpathMetadatumContributor { + + private static final Logger log = LoggerFactory.getLogger(WosIdentifierRidContributor.class); + + @Override + public Collection contributeMetadata(Element element) { + List values = new LinkedList<>(); + try { + for (String ns : prefixToNamespaceMapping.keySet()) { + List nodes = element.getChildren(query, Namespace.getNamespace(ns)); + for (Element el : nodes) { + // Element element2 = el.getFirstChildWithName("name"); + if (Objects.nonNull(element)) { + String type = element.getAttributeValue("role"); + setIdentyfire(type, element, values); + } + } + } + return values; + } catch (JaxenException e) { + log.error(query, e); + throw new RuntimeException(e); + } + } + + private void setIdentyfire(String type, Element el, List values) throws JaxenException { + if (StringUtils.equals("researcher_id", type)) { + String value = el.getAttributeValue("r_id"); + if (StringUtils.isNotBlank(value)) { + values.add(metadataFieldMapping.toDCValue(this.field, value)); + } else { + values.add(metadataFieldMapping.toDCValue(this.field, + Constants.PLACEHOLDER_PARENT_METADATA_VALUE)); + } + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSFieldMapping.java new file mode 100644 index 0000000000..39f409c520 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSFieldMapping.java @@ -0,0 +1,37 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.wos.service; +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 WOS metadatum fields on the DSpace metadatum fields + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4science dot it) + */ +@SuppressWarnings("rawtypes") +public class WOSFieldMapping 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 + @SuppressWarnings("unchecked") + @Resource(name = "wosMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..75e006431a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java @@ -0,0 +1,270 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.wos.service; + +import java.io.IOException; +import java.io.StringReader; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.el.MethodNotFoundException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.scopus.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.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements a data source for querying WOS + * + * @author Boychuk Mykhaylo (boychuk.mykhaylo at 4Science dot it) + */ +public class WOSImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + + private static final String AI_PATTERN = "^AI=(.*)"; + private static final Pattern ISI_PATTERN = Pattern.compile("^\\d{15}$"); + private static final String ENDPOINT_SEARCH_BY_ID_WOS = "https://wos-api.clarivate.com/api/wos/id/"; + private static final String ENDPOINT_SEARCH_WOS = "https://wos-api.clarivate.com/api/wos/?databaseId=WOS&lang=en&usrQuery="; + + private int timeout = 1000; + + private String apiKey; + + @Autowired + private LiveImportClient liveImportClient; + + @Override + public void init() throws Exception {} + + /** + * The string that identifies this import implementation. Preferable a URI + * + * @return the identifying uri + */ + @Override + public String getImportSource() { + return "wos"; + } + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query, count, start)); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = retry(new SearchByQueryCallable(query)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + List records = retry(new FindByIdCallable(id)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new SearchNBByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for WOS"); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for WOS"); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for WOS"); + } + + /** + * This class implements a callable to get the numbers of result + */ + private class SearchNBByQueryCallable implements Callable { + + private String query; + + private SearchNBByQueryCallable(String queryString) { + this.query = queryString; + } + + private SearchNBByQueryCallable(Query query) { + this.query = query.getParameterAsClass("query", String.class); + } + + @Override + public Integer call() throws Exception { + if (StringUtils.isNotBlank(apiKey)) { + String queryString = URLEncoder.encode(checkQuery(query), StandardCharsets.UTF_8); + String url = ENDPOINT_SEARCH_WOS + queryString + "&count=1&firstRecord=1"; + String response = liveImportClient.executeHttpGetRequest(timeout, url, getRequestParameters()); + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(response)); + Element root = document.getRootElement(); + XPathExpression xpath = XPathFactory.instance().compile("QueryResult/RecordsFound", + Filters.element(), null); + Element tot = xpath.evaluateFirst(root); + return Integer.valueOf(tot.getValue()); + } + return null; + } + } + + private class FindByIdCallable implements Callable> { + + private String doi; + + private FindByIdCallable(String doi) { + this.doi = URLEncoder.encode(doi, StandardCharsets.UTF_8); + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + if (StringUtils.isNotBlank(apiKey)) { + String url = ENDPOINT_SEARCH_BY_ID_WOS + this.doi + "?databaseId=WOS&lang=en&count=10&firstRecord=1"; + String response = liveImportClient.executeHttpGetRequest(timeout, url, getRequestParameters()); + List elements = splitToRecords(response); + for (Element record : elements) { + results.add(transformSourceRecords(record)); + } + } + return results; + } + } + + private class SearchByQueryCallable implements Callable> { + private Query query; + + + private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { + query = new Query(); + query.addParameter("query", queryString); + query.addParameter("start", start); + query.addParameter("count", maxResult); + } + + private SearchByQueryCallable(Query query) { + this.query = query; + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + String queryString = checkQuery(query.getParameterAsClass("query", String.class)); + Integer start = query.getParameterAsClass("start", Integer.class); + Integer count = query.getParameterAsClass("count", Integer.class); + if (StringUtils.isNotBlank(apiKey)) { + String url = ENDPOINT_SEARCH_WOS + URLEncoder.encode(queryString, StandardCharsets.UTF_8) + + "&count=" + count + "&firstRecord=" + (start + 1); + String response = liveImportClient.executeHttpGetRequest(timeout, url, getRequestParameters()); + List omElements = splitToRecords(response); + for (Element el : omElements) { + results.add(transformSourceRecords(el)); + } + } + return results; + } + + } + + private Map getRequestParameters() { + Map params = new HashMap(); + params.put("Accept", "application/xml"); + params.put("X-ApiKey", apiKey); + return params; + } + + private String checkQuery(String query) { + Pattern risPattern = Pattern.compile(AI_PATTERN); + Matcher risMatcher = risPattern.matcher(query.trim()); + if (risMatcher.matches()) { + return query; + } + if (DoiCheck.isDoi(query)) { + // FIXME: workaround to be removed once fixed by the community the double post of query param + if (query.startsWith(",")) { + query = query.substring(1); + } + return "DO=(" + query + ")"; + } else if (isIsi(query)) { + return "UT=(" + query + ")"; + } + StringBuilder queryBuilder = new StringBuilder("TS=("); + queryBuilder.append(query).append(")"); + return queryBuilder.toString(); + } + + private boolean isIsi(String query) { + if (query.startsWith("WOS:")) { + return true; + } + Matcher matcher = ISI_PATTERN.matcher(query.trim()); + return matcher.matches(); + } + + private List splitToRecords(String recordsSrc) { + try { + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(recordsSrc)); + Element root = document.getRootElement(); + XPathExpression xpath = XPathFactory.instance().compile("Data/Records/records/REC", + Filters.element(), null); + List records = xpath.evaluate(root); + if (Objects.nonNull(records)) { + return records; + } + } catch (JDOMException | IOException e) { + return new ArrayList(); + } + return new ArrayList(); + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + +} \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-ex.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-ex.xml index 84fd7e71e2..cd50281f38 100644 --- a/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-ex.xml +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/scopus-ex.xml @@ -26,7 +26,6 @@ 2023-01-01 2023 10.3934/mine.2023004 - In this paper, dedicated to Ireneo Peral, we study the regularizing effect of some lower order terms in Dirichlet problems despite the presence of Hardy potentials in the right hand side. 0 https://api.elsevier.com/content/affiliation/affiliation_id/60032350 @@ -109,7 +108,6 @@ 2023-01-01 2023 10.3934/mine.2023001 - We analyze the large deviations for a discrete energy Kac-like walk. In particular, we exhibit a path, with probability exponentially small in the number of particles, that looses energy. 0 https://api.elsevier.com/content/affiliation/affiliation_id/60032350 diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/wos-responce.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/wos-responce.xml new file mode 100644 index 0000000000..dc2341b9f8 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/wos-responce.xml @@ -0,0 +1,469 @@ + + + + + + + WOS:000551093100034 + + + + + + + + 225-234 + + + EDULEARN19: 11TH INTERNATIONAL CONFERENCE ON + EDUCATION AND NEW LEARNING TECHNOLOGIES + EDULEARN Proceedings + EDULEARN PROC + EDULEARN PR + EDULEARN PROC + MENTORING IN EDUCATION. FEMALE ROLE MODELS IN + ITALIAN DESIGN ACADEMIC CULTURE + EDULEARN Proceedings + + + + Bollini, Letizia + Bollini, Letizia + Bollini, L + Letizia + Bollini + + + Chova, LG + Chova, LG + Chova, LG + LG + Chova + + + Martinez, AL + Martinez, AL + Martinez, AL + AL + Martinez + + + Torres, IC + Torres, IC + Torres, IC + IC + Torres + + + + Proceedings Paper + + + + + 11th International Conference on Education and New + Learning Technologies (EDULEARN), JUL 01-03, 2019, Palma, + SPAIN + + + 11th International Conference on Education and New + Learning Technologies (EDULEARN) + + + JUL 01-03, + 2019 + + + + Palma + SPAIN + + + + + + + + LAURI VOLPI 6, VALENICA, BURJASSOT 46100, SPAIN + VALENICA + + + + IATED-INT ASSOC TECHNOLOGY EDUCATION & + DEVELOPMENT + IATED-INT ASSOC TECHNOLOGY EDUCATION & + DEVELOPMENT + + + + + + + + English + + + English + + + Meeting + + + + + + Univ Milano Bicocca, Dept Psychol, Milan, Italy + + Univ Milano Bicocca + University of Milano-Bicocca + + + Dept Psychol + + Milan + Italy + + + + Bollini, Letizia + Bollini, Letizia + Bollini, L + Letizia + Bollini + + + + + + + + Univ Milano Bicocca, Dept Psychol, Milan, Italy + + Univ Milano Bicocca + University of Milano-Bicocca + + + Dept Psychol + + Milan + Italy + + + + Bollini, Letizia + Bollini, Letizia + Bollini, L + Letizia + Bollini + + + + + + + Social Sciences + + + Education & + Educational Research + Education & Educational Research + + + + Design education + female role models + mentoring in education + + + + +

    It is widely recognized that mentors and tutors are + empowering roles in education systems. Students can benefit + to be raised in an inclusive environment in which senior + peers or professors play a positive and significant role + model. In particular, looking at the field of design studies, + a tradition of collaborative and collective culture shared + between students and teachers can be identified both in the + Bauhaus and in the Hochschule fur Gestaltung Ulm experiences. + In more recent time, studies show how junior designers + benefit from the presence, and the confront of significant + grown-up or more experienced figures able to listen to, to + coach and to mentor them. Nevertheless, when we look at the + issue from a gender perspective, we may notice a lack of + examples and reference figures, even if female students are a + significant percentage, when not the majority, in the + design's bachelors and masters degrees university + courses. Moreover, in Italy - at the 82 place on 144 in the + worldwide ranking for gender equality and women rights + according to the "Global Gender Gap Index 2017" - + the situation seems to be more difficult than in the other + European or developed countries, also due to the + representation of female roles in society and female bodies + in mass media. On the one hand, the paper offers a deeper + insight into the problem. The results of a census in the + field of digital product design - including human-centered, + interaction, user experience and interface design - are + mapped and discussed to understand the presence of female + professors and their role in BA/BS and MD courses. On the + other hand, the paper proposes some possible initiatives to + spread consciousness and participation and to improve some + changes in academic design culture.

    +
    +
    +
    +
    + + BP4FG + : 225-234 2019 + 5526 + + VISUAL DESIGN + EXPERIENCE + + 5526 + + Figures + Plates + Color plates + + + P + IATED-INT ASSOC TECHNOLOGY EDUCATION & + DEVELOPMENT, LAURI VOLPI 6, VALENICA, BURJASSOT 46100, SPAIN + N + + +
    + + + + + + + + + + + + + +
    + + WOS:000551093100033 + + + + + + + + 224-224 + + + EDULEARN19: 11TH INTERNATIONAL CONFERENCE ON + EDUCATION AND NEW LEARNING TECHNOLOGIES + EDULEARN Proceedings + EDULEARN PROC + EDULEARN PR + EDULEARN PROC + DYSLEXIA AND TYPOGRAPHY: HOW TO IMPROVE + EDUCATIONAL PUBLISHING FOR INCLUSIVE DESIGN - A CRITICAL REVIEW + EDULEARN Proceedings + + + + Bollini, L. + Bollini, L. + Bollini, L + L. + Bollini + + + Chova, LG + Chova, LG + Chova, LG + LG + Chova + + + Martinez, AL + Martinez, AL + Martinez, AL + AL + Martinez + + + Torres, IC + Torres, IC + Torres, IC + IC + Torres + + + + Proceedings Paper + + + + + 11th International Conference on Education and New + Learning Technologies (EDULEARN), JUL 01-03, 2019, Palma, + SPAIN + + + 11th International Conference on Education and New + Learning Technologies (EDULEARN) + + + JUL 01-03, + 2019 + + + + Palma + SPAIN + + + + + + + + LAURI VOLPI 6, VALENICA, BURJASSOT 46100, SPAIN + VALENICA + + + + IATED-INT ASSOC TECHNOLOGY EDUCATION & + DEVELOPMENT + IATED-INT ASSOC TECHNOLOGY EDUCATION & + DEVELOPMENT + + + + + + + + English + + + English + + + Meeting + + + + + + Univ Milano Bicocca, Milan, Italy + + Univ Milano Bicocca + University of Milano-Bicocca + + Milan + Italy + + + + Bollini, L. + Bollini, L. + Bollini, L + L. + Bollini + + + + + + + + Univ Milano Bicocca, Milan, Italy + + Univ Milano Bicocca + University of Milano-Bicocca + + Milan + Italy + + + + Bollini, L. + Bollini, L. + Bollini, L + L. + Bollini + + + + + + + Social Sciences + + + Education & + Educational Research + Education & Educational Research + + + + Dyslexia and Typography + Font design + educational publishing + Design for all + Universal design + + + + BP4FG + : 224-224 2019 + 5526 + 5526 + + Figures + Plates + Color plates + + + P + IATED-INT ASSOC TECHNOLOGY EDUCATION & + DEVELOPMENT, LAURI VOLPI 6, VALENICA, BURJASSOT 46100, SPAIN + N + + + + + + + + + + + + + + + + + +
    +
    +
    + + 1 + 64666906 + 2 + + undefined +
    \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.java new file mode 100644 index 0000000000..ca3195b344 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AbstractLiveImportIntegrationTest.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; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.UnsupportedEncodingException; +import java.util.Collection; + +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.tools.ant.filters.StringInputStream; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class AbstractLiveImportIntegrationTest extends AbstractControllerIntegrationTest { + + protected boolean matchRecords(Collection recordsImported, Collection records2match) { + ImportRecord firstImported = recordsImported.iterator().next(); + ImportRecord secondImported = recordsImported.iterator().next(); + ImportRecord first2match = recordsImported.iterator().next(); + ImportRecord second2match = recordsImported.iterator().next(); + boolean checkFirstRecord = firstImported.getValueList().containsAll(first2match.getValueList()); + boolean checkSecondRecord = secondImported.getValueList().containsAll(second2match.getValueList()); + return checkFirstRecord && checkSecondRecord; + } + + protected MetadatumDTO createMetadatumDTO(String schema, String element, String qualifier, String value) { + MetadatumDTO metadatumDTO = new MetadatumDTO(); + metadatumDTO.setSchema(schema); + metadatumDTO.setElement(element); + metadatumDTO.setQualifier(qualifier); + metadatumDTO.setValue(value); + return metadatumDTO; + } + + protected CloseableHttpResponse mockResponse(String xmlExample, int statusCode, String reason) + throws UnsupportedEncodingException { + BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); + basicHttpEntity.setChunked(true); + basicHttpEntity.setContent(new StringInputStream(xmlExample)); + + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(statusLine(statusCode, reason)); + when(response.getEntity()).thenReturn(basicHttpEntity); + return response; + } + + protected StatusLine statusLine(int statusCode, String reason) { + return new StatusLine() { + @Override + public ProtocolVersion getProtocolVersion() { + return new ProtocolVersion("http", 1, 1); + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getReasonPhrase() { + return reason; + } + }; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedEuropeMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedEuropeMetadataSourceServiceIT.java index 78a1777370..d6b3cc8143 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedEuropeMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/PubmedEuropeMetadataSourceServiceIT.java @@ -9,11 +9,9 @@ package org.dspace.app.rest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.FileInputStream; -import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; @@ -21,13 +19,8 @@ import java.util.LinkedList; import java.util.List; import org.apache.commons.io.IOUtils; -import org.apache.http.ProtocolVersion; -import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.entity.BasicHttpEntity; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.tools.ant.filters.StringInputStream; -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.metadatamapping.MetadatumDTO; import org.dspace.importer.external.pubmedeurope.PubmedEuropeMetadataSourceServiceImpl; @@ -42,7 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ -public class PubmedEuropeMetadataSourceServiceIT extends AbstractControllerIntegrationTest { +public class PubmedEuropeMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { @Autowired private PubmedEuropeMetadataSourceServiceImpl pubmedEuropeMetadataServiceImpl; @@ -60,13 +53,13 @@ public class PubmedEuropeMetadataSourceServiceIT extends AbstractControllerInteg String path2file = testProps.get("test.pubmedeurope-empty").toString(); try (FileInputStream file = new FileInputStream(path)) { try (FileInputStream file2 = new FileInputStream(path2file)) { - String xmlMetricsExample = IOUtils.toString(file, Charset.defaultCharset()); - String xmlMetricsExample2 = IOUtils.toString(file2, Charset.defaultCharset()); + String pubmedEuropeXmlResp = IOUtils.toString(file, Charset.defaultCharset()); + String pubmedEuropeXmlResp2 = IOUtils.toString(file2, Charset.defaultCharset()); liveImportClientImpl.setHttpClient(httpClient); - CloseableHttpResponse response = mockResponse(xmlMetricsExample, 200, "OK"); - CloseableHttpResponse response2 = mockResponse(xmlMetricsExample2, 200, "OK"); + CloseableHttpResponse response = mockResponse(pubmedEuropeXmlResp, 200, "OK"); + CloseableHttpResponse response2 = mockResponse(pubmedEuropeXmlResp2, 200, "OK"); when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response, response2); @@ -81,26 +74,26 @@ public class PubmedEuropeMetadataSourceServiceIT extends AbstractControllerInteg } } - private boolean matchRecords(Collection recordsImported, Collection records2match) { - ImportRecord firstImported = recordsImported.iterator().next(); - ImportRecord secondImported = recordsImported.iterator().next(); - ImportRecord first2match = recordsImported.iterator().next(); - ImportRecord second2match = recordsImported.iterator().next(); - boolean checkFirstRecord = firstImported.getValueList().containsAll(first2match.getValueList()); - boolean checkSecondRecord = secondImported.getValueList().containsAll(second2match.getValueList()); - return checkFirstRecord && checkSecondRecord; - } + @Test + public void pubmedEuropeImportMetadataGetRecordsCountTest() throws Exception { + context.turnOffAuthorisationSystem(); - private CloseableHttpResponse mockResponse(String xmlExample, int statusCode, String reason) - throws UnsupportedEncodingException { - BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); - basicHttpEntity.setChunked(true); - basicHttpEntity.setContent(new StringInputStream(xmlExample)); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.pubmedeurope").toString(); + try (FileInputStream file = new FileInputStream(path)) { + String pubmedEuropeXmlResp = IOUtils.toString(file, Charset.defaultCharset()); - CloseableHttpResponse response = mock(CloseableHttpResponse.class); - when(response.getStatusLine()).thenReturn(statusLine(statusCode, reason)); - when(response.getEntity()).thenReturn(basicHttpEntity); - return response; + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(pubmedEuropeXmlResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + int tot = pubmedEuropeMetadataServiceImpl.getRecordsCount("test query"); + assertEquals(3, tot); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } } private Collection getRecords() { @@ -217,32 +210,4 @@ public class PubmedEuropeMetadataSourceServiceIT extends AbstractControllerInteg return records; } - private MetadatumDTO createMetadatumDTO(String schema, String element, String qualifier, String value) { - MetadatumDTO metadatumDTO = new MetadatumDTO(); - metadatumDTO.setSchema(schema); - metadatumDTO.setElement(element); - metadatumDTO.setQualifier(qualifier); - metadatumDTO.setValue(value); - return metadatumDTO; - } - - private StatusLine statusLine(int statusCode, String reason) { - return new StatusLine() { - @Override - public ProtocolVersion getProtocolVersion() { - return new ProtocolVersion("http", 1, 1); - } - - @Override - public int getStatusCode() { - return statusCode; - } - - @Override - public String getReasonPhrase() { - return reason; - } - }; - } - } \ No newline at end of file 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 a3ec481a21..bf01c71f83 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 @@ -9,11 +9,9 @@ package org.dspace.app.rest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.FileInputStream; -import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; @@ -22,13 +20,8 @@ import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.http.ProtocolVersion; -import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.entity.BasicHttpEntity; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.tools.ant.filters.StringInputStream; -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.metadatamapping.MetadatumDTO; import org.dspace.importer.external.scopus.service.LiveImportClientImpl; @@ -43,7 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ -public class ScopusImportMetadataSourceServiceIT extends AbstractControllerIntegrationTest { +public class ScopusImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { @Autowired private ScopusImportMetadataSourceServiceImpl scopusServiceImpl; @@ -62,12 +55,10 @@ public class ScopusImportMetadataSourceServiceIT extends AbstractControllerInteg CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); String path = testProps.get("test.scopus").toString(); try (FileInputStream file = new FileInputStream(path)) { - String xmlMetricsExample = IOUtils.toString(file, Charset.defaultCharset()); + String scopusXmlResp = IOUtils.toString(file, Charset.defaultCharset()); liveImportClientImpl.setHttpClient(httpClient); - - CloseableHttpResponse response = mockResponse(xmlMetricsExample, 200, "OK"); - + CloseableHttpResponse response = mockResponse(scopusXmlResp, 200, "OK"); when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); context.restoreAuthSystemState(); @@ -92,12 +83,10 @@ public class ScopusImportMetadataSourceServiceIT extends AbstractControllerInteg CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); String path = testProps.get("test.scopus").toString(); try (FileInputStream file = new FileInputStream(path)) { - String xmlMetricsExample = IOUtils.toString(file, Charset.defaultCharset()); + String scopusXmlResp = IOUtils.toString(file, Charset.defaultCharset()); liveImportClientImpl.setHttpClient(httpClient); - - CloseableHttpResponse response = mockResponse(xmlMetricsExample, 200, "OK"); - + CloseableHttpResponse response = mockResponse(scopusXmlResp, 200, "OK"); when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); context.restoreAuthSystemState(); @@ -120,12 +109,10 @@ public class ScopusImportMetadataSourceServiceIT extends AbstractControllerInteg CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); String path = testProps.get("test.scopus-empty").toString(); try (FileInputStream file = new FileInputStream(path)) { - String xmlMetricsExample = IOUtils.toString(file, Charset.defaultCharset()); + String scopusXmlResp = IOUtils.toString(file, Charset.defaultCharset()); liveImportClientImpl.setHttpClient(httpClient); - - CloseableHttpResponse response = mockResponse(xmlMetricsExample, 200, "OK"); - + CloseableHttpResponse response = mockResponse(scopusXmlResp, 200, "OK"); when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); context.restoreAuthSystemState(); @@ -138,37 +125,40 @@ public class ScopusImportMetadataSourceServiceIT extends AbstractControllerInteg } } - private boolean matchRecords(Collection recordsImported, Collection records2match) { - ImportRecord firstImported = recordsImported.iterator().next(); - ImportRecord secondImported = recordsImported.iterator().next(); - ImportRecord first2match = recordsImported.iterator().next(); - ImportRecord second2match = recordsImported.iterator().next(); - boolean checkFirstRecord = firstImported.getValueList().containsAll(first2match.getValueList()); - boolean checkSecondRecord = secondImported.getValueList().containsAll(second2match.getValueList()); - return checkFirstRecord && checkSecondRecord; - } - private Collection getRecords() { Collection records = new LinkedList(); - List metadatums = new ArrayList(); //define first record + List metadatums = new ArrayList(); + MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023004"); MetadatumDTO title = createMetadatumDTO("dc","title", null, "Hardy potential versus lower order terms in Dirichlet problems: regularizing effects"); - MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023004"); - MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Journal"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); MetadatumDTO citationVolume = createMetadatumDTO("oaire", "citation", "volume", "5"); MetadatumDTO citationIssue = createMetadatumDTO("oaire", "citation", "issue", "1"); MetadatumDTO scopusId = createMetadatumDTO("dc", "identifier", "scopus", "2-s2.0-85124241875"); MetadatumDTO funding = createMetadatumDTO("dc", "relation", "funding", "Junta de Andalucía"); MetadatumDTO grantno = createMetadatumDTO("dc", "relation", "grantno", "PGC2018-096422-B-I00"); MetadatumDTO subject = createMetadatumDTO("dc", "subject", null, - "Hardy potentials | Laplace equation | Summability of solutions"); + "Hardy potentials | Laplace equation | Summability of solutions"); + MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "Arcoya D."); + MetadatumDTO scopusAuthorId = createMetadatumDTO("person", "identifier", "scopus-author-id", "6602330574"); + MetadatumDTO orcid = createMetadatumDTO("person", "identifier", "orcid", "#PLACEHOLDER_PARENT_METADATA_VALUE#"); + MetadatumDTO orgunit = createMetadatumDTO("oairecerif", "affiliation", "orgunit", "Universidad de Granada"); + MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Boccardo L."); + MetadatumDTO scopusAuthorId2 = createMetadatumDTO("person", "identifier", "scopus-author-id", "7003612261"); + MetadatumDTO orcid2 = createMetadatumDTO("person", "identifier", "orcid","#PLACEHOLDER_PARENT_METADATA_VALUE#"); + MetadatumDTO orgunit2 = createMetadatumDTO("oairecerif", "affiliation","orgunit","Sapienza Università di Roma"); + MetadatumDTO author3 = createMetadatumDTO("dc", "contributor", "author", "Orsina L."); + MetadatumDTO scopusAuthorId3 = createMetadatumDTO("person", "identifier", "scopus-author-id", "6602595438"); + MetadatumDTO orcid3 = createMetadatumDTO("person", "identifier", "orcid","#PLACEHOLDER_PARENT_METADATA_VALUE#"); + MetadatumDTO orgunit3 = createMetadatumDTO("oairecerif", "affiliation","orgunit","Sapienza Università di Roma"); MetadatumDTO rights = createMetadatumDTO("dc", "rights", null, "open access"); - MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", - "Mathematics In Engineering"); - metadatums.add(title); + MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", "Mathematics In Engineering"); + MetadatumDTO ispartofseries = createMetadatumDTO("dc","relation","ispartofseries","Mathematics In Engineering"); + metadatums.add(doi); + metadatums.add(title); metadatums.add(date); metadatums.add(type); metadatums.add(citationVolume); @@ -177,13 +167,28 @@ public class ScopusImportMetadataSourceServiceIT extends AbstractControllerInteg metadatums.add(funding); metadatums.add(grantno); metadatums.add(subject); + metadatums.add(author); + metadatums.add(scopusAuthorId); + metadatums.add(orcid); + metadatums.add(orgunit); + metadatums.add(author2); + metadatums.add(scopusAuthorId2); + metadatums.add(orcid2); + metadatums.add(orgunit2); + metadatums.add(author3); + metadatums.add(scopusAuthorId3); + metadatums.add(orcid3); + metadatums.add(orgunit3); metadatums.add(rights); metadatums.add(ispartof); + metadatums.add(ispartofseries); ImportRecord firstrRecord = new ImportRecord(metadatums); + //define second record + List metadatums2 = new ArrayList(); + MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023001"); MetadatumDTO title2 = createMetadatumDTO("dc","title", null, "Large deviations for a binary collision model: energy evaporation"); - MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.3934/mine.2023001"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "Journal"); MetadatumDTO citationVolume2 = createMetadatumDTO("oaire", "citation", "volume", "5"); @@ -192,64 +197,59 @@ public class ScopusImportMetadataSourceServiceIT extends AbstractControllerInteg MetadatumDTO grantno2 = createMetadatumDTO("dc", "relation", "grantno", "undefined"); MetadatumDTO subject2 = createMetadatumDTO("dc", "subject", null, "Boltzmann equation | Discrete energy model | Kac model | Large deviations | Violation of energy conservation"); + + MetadatumDTO author4 = createMetadatumDTO("dc", "contributor", "author", "Basile G."); + MetadatumDTO scopusAuthorId4 = createMetadatumDTO("person", "identifier", "scopus-author-id", "55613229065"); + MetadatumDTO orcid4 = createMetadatumDTO("person", "identifier", "orcid","#PLACEHOLDER_PARENT_METADATA_VALUE#"); + MetadatumDTO orgunit4 = createMetadatumDTO("oairecerif", "affiliation","orgunit","Sapienza Università di Roma"); + MetadatumDTO author5 = createMetadatumDTO("dc", "contributor", "author", "Benedetto D."); + MetadatumDTO scopusAuthorId5 = createMetadatumDTO("person", "identifier", "scopus-author-id", "55893665100"); + MetadatumDTO orcid5 = createMetadatumDTO("person", "identifier", "orcid","#PLACEHOLDER_PARENT_METADATA_VALUE#"); + MetadatumDTO orgunit5 = createMetadatumDTO("oairecerif", "affiliation","orgunit","Sapienza Università di Roma"); + MetadatumDTO author6 = createMetadatumDTO("dc", "contributor", "author", "Caglioti E."); + MetadatumDTO scopusAuthorId6 = createMetadatumDTO("person", "identifier", "scopus-author-id", "7004588675"); + MetadatumDTO orcid6 = createMetadatumDTO("person", "identifier", "orcid","#PLACEHOLDER_PARENT_METADATA_VALUE#"); + MetadatumDTO orgunit6 = createMetadatumDTO("oairecerif", "affiliation","orgunit","Sapienza Università di Roma"); + MetadatumDTO author7 = createMetadatumDTO("dc", "contributor", "author", "Bertini L."); + MetadatumDTO scopusAuthorId7 = createMetadatumDTO("person", "identifier", "scopus-author-id", "7005555198"); + MetadatumDTO orcid7 = createMetadatumDTO("person", "identifier", "orcid","#PLACEHOLDER_PARENT_METADATA_VALUE#"); + MetadatumDTO orgunit7 = createMetadatumDTO("oairecerif", "affiliation","orgunit","Sapienza Università di Roma"); MetadatumDTO rights2 = createMetadatumDTO("dc", "rights", null, "open access"); - MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", - "Mathematics In Engineering"); - metadatums.add(title2); - metadatums.add(doi2); - metadatums.add(date2); - metadatums.add(type2); - metadatums.add(citationVolume2); - metadatums.add(citationIssue2); - metadatums.add(scopusId2); - metadatums.add(grantno2); - metadatums.add(subject2); - metadatums.add(rights2); - metadatums.add(ispartof2); - ImportRecord secondRecord = new ImportRecord(metadatums); + MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", "Mathematics In Engineering"); + MetadatumDTO ispartofseries2 = createMetadatumDTO("dc", "relation", "ispartofseries", + "Mathematics In Engineering"); + metadatums2.add(title2); + metadatums2.add(doi2); + metadatums2.add(date2); + metadatums2.add(type2); + metadatums2.add(citationVolume2); + metadatums2.add(citationIssue2); + metadatums2.add(scopusId2); + metadatums2.add(grantno2); + metadatums2.add(subject2); + metadatums2.add(author4); + metadatums2.add(scopusAuthorId4); + metadatums2.add(orcid4); + metadatums2.add(orgunit4); + metadatums2.add(author5); + metadatums2.add(scopusAuthorId5); + metadatums2.add(orcid5); + metadatums2.add(orgunit5); + metadatums2.add(author6); + metadatums2.add(scopusAuthorId6); + metadatums2.add(orcid6); + metadatums2.add(orgunit6); + metadatums2.add(author7); + metadatums2.add(scopusAuthorId7); + metadatums2.add(orcid7); + metadatums2.add(orgunit7); + metadatums2.add(rights2); + metadatums2.add(ispartof2); + metadatums2.add(ispartofseries2); + ImportRecord secondRecord = new ImportRecord(metadatums2); records.add(firstrRecord); records.add(secondRecord); return records; } - private MetadatumDTO createMetadatumDTO(String schema, String element, String qualifier, String value) { - MetadatumDTO metadatumDTO = new MetadatumDTO(); - metadatumDTO.setSchema(schema); - metadatumDTO.setElement(element); - metadatumDTO.setQualifier(qualifier); - metadatumDTO.setValue(value); - return metadatumDTO; - } - - private CloseableHttpResponse mockResponse(String xmlExample, int statusCode, String reason) - throws UnsupportedEncodingException { - BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); - basicHttpEntity.setChunked(true); - basicHttpEntity.setContent(new StringInputStream(xmlExample)); - - CloseableHttpResponse response = mock(CloseableHttpResponse.class); - when(response.getStatusLine()).thenReturn(statusLine(statusCode, reason)); - when(response.getEntity()).thenReturn(basicHttpEntity); - return response; - } - - private StatusLine statusLine(int statusCode, String reason) { - return new StatusLine() { - @Override - public ProtocolVersion getProtocolVersion() { - return new ProtocolVersion("http", 1, 1); - } - - @Override - public int getStatusCode() { - return statusCode; - } - - @Override - public String getReasonPhrase() { - return reason; - } - }; - } - } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WOSImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WOSImportMetadataSourceServiceIT.java new file mode 100644 index 0000000000..38c605e068 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WOSImportMetadataSourceServiceIT.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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.scopus.service.LiveImportClientImpl; +import org.dspace.importer.external.wos.service.WOSImportMetadataSourceServiceImpl; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link WOSImportMetadataSourceServiceImpl} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class WOSImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { + + @Autowired + private WOSImportMetadataSourceServiceImpl wosImportMetadataService; + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Test + public void wosImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + String originApiKey = wosImportMetadataService.getApiKey(); + if (StringUtils.isBlank(originApiKey)) { + wosImportMetadataService.setApiKey("testApiKey"); + } + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path2file = testProps.get("test.wos").toString(); + try (FileInputStream file = new FileInputStream(path2file)) { + String wosXmlResp = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + + CloseableHttpResponse response = mockResponse(wosXmlResp, 200, "OK"); + + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + Collection recordsImported = wosImportMetadataService.getRecords("test query", 0, 2); + Collection collection2match = getRecords(); + assertEquals(2, recordsImported.size()); + assertTrue(matchRecords(recordsImported, collection2match)); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + wosImportMetadataService.setApiKey(originApiKey); + } + } + + @Test + public void wosImportMetadataGetRecordsCountTest() throws Exception { + context.turnOffAuthorisationSystem(); + String originApiKey = wosImportMetadataService.getApiKey(); + if (StringUtils.isBlank(originApiKey)) { + wosImportMetadataService.setApiKey("testApiKey"); + } + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.wos").toString(); + try (FileInputStream file = new FileInputStream(path)) { + String wosXmlResp = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(wosXmlResp, 200, "OK"); + + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + int tot = wosImportMetadataService.getRecordsCount("test query"); + assertEquals(2, tot); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + wosImportMetadataService.setApiKey(originApiKey); + } + } + + private Collection getRecords() { + Collection records = new LinkedList(); + List metadatums = new ArrayList(); + //define first record + MetadatumDTO edition = createMetadatumDTO("oaire","citation", "edition", "WOS.ISSHP"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2019"); + MetadatumDTO startPage = createMetadatumDTO("oaire","citation", "startPage", "225"); + MetadatumDTO endPage = createMetadatumDTO("oaire","citation", "endPage", "234"); + MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Book in series"); + MetadatumDTO issn = createMetadatumDTO("dc", "relation", "issn", "2340-1117"); + MetadatumDTO isbn = createMetadatumDTO("dc", "identifier", "isbn", "978-84-09-12031-4"); + MetadatumDTO iso = createMetadatumDTO("dc", "language", "iso", "1"); + MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "Bollini, Letizia"); + MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Chova, LG"); + MetadatumDTO author3 = createMetadatumDTO("dc", "contributor", "author", "Martinez, AL"); + MetadatumDTO author4 = createMetadatumDTO("dc", "contributor", "author", "Torres, IC"); + MetadatumDTO isi = createMetadatumDTO("dc", "identifier", "isi", "WOS:000551093100034"); + metadatums.add(edition); + metadatums.add(date); + metadatums.add(startPage); + metadatums.add(endPage); + metadatums.add(type); + metadatums.add(issn); + metadatums.add(isbn); + metadatums.add(iso); + metadatums.add(author); + metadatums.add(author2); + metadatums.add(author3); + metadatums.add(author4); + metadatums.add(isi); + ImportRecord firstrRecord = new ImportRecord(metadatums); + + //define second record + List metadatums2 = new ArrayList(); + MetadatumDTO edition2 = createMetadatumDTO("oaire","citation", "edition", "WOS.ISSHP"); + MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2019"); + MetadatumDTO startPage2 = createMetadatumDTO("oaire","citation", "startPage", "224"); + MetadatumDTO endPage2 = createMetadatumDTO("oaire","citation", "endPage", "224"); + MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "Book in series"); + MetadatumDTO issn2 = createMetadatumDTO("dc", "relation", "issn", "2340-1117"); + MetadatumDTO isbn2 = createMetadatumDTO("dc", "identifier", "isbn", "978-84-09-12031-4"); + MetadatumDTO iso2 = createMetadatumDTO("dc", "language", "iso", "1"); + MetadatumDTO author5 = createMetadatumDTO("dc", "contributor", "author", "Bollini, Letizia"); + MetadatumDTO author6 = createMetadatumDTO("dc", "contributor", "author", "Chova, LG"); + MetadatumDTO author7 = createMetadatumDTO("dc", "contributor", "author", "Martinez, AL"); + MetadatumDTO author8 = createMetadatumDTO("dc", "contributor", "author", "Torres, IC"); + MetadatumDTO isi2 = createMetadatumDTO("dc", "identifier", "isi", "WOS:000551093100033"); + metadatums2.add(edition2); + metadatums2.add(date2); + metadatums2.add(startPage2); + metadatums2.add(endPage2); + metadatums2.add(type2); + metadatums2.add(issn2); + metadatums2.add(isbn2); + metadatums2.add(iso2); + metadatums2.add(author5); + metadatums2.add(author6); + metadatums2.add(author7); + metadatums2.add(author8); + metadatums2.add(isi2); + ImportRecord secondRecord = new ImportRecord(metadatums2); + + records.add(firstrRecord); + records.add(secondRecord); + return records; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/test-config.properties b/dspace-server-webapp/src/test/resources/test-config.properties index ac04d032d6..8ff06d65f5 100644 --- a/dspace-server-webapp/src/test/resources/test-config.properties +++ b/dspace-server-webapp/src/test/resources/test-config.properties @@ -17,4 +17,5 @@ test.curateTaskFile = ./target/testing/dspace/assetstore/curate.txt test.scopus = ./target/testing/dspace/assetstore/scopus-ex.xml test.scopus-empty = ./target/testing/dspace/assetstore/scopus-empty-resp.xml test.pubmedeurope = ./target/testing/dspace/assetstore/pubmedeurope-test.xml -test.pubmedeurope-empty = ./target/testing/dspace/assetstore/pubmedeurope-empty.xml \ No newline at end of file +test.pubmedeurope-empty = ./target/testing/dspace/assetstore/pubmedeurope-empty.xml +test.wos = ./target/testing/dspace/assetstore/wos-responce.xml \ No newline at end of file diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 8b985533c2..78a2f7225e 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1598,7 +1598,11 @@ scopus.instToken = scopus.search-api.viewMode = COMPLETE #################################################################### +#------------------- WOS SERVICES ---------------------------------# +#------------------------------------------------------------------# +wos.apiKey = +#################################################################### # Load default module configs # ---------------------------- # To exclude a module configuration, simply comment out its "include" statement. diff --git a/dspace/config/spring/api/wos-integration.xml b/dspace/config/spring/api/wos-integration.xml new file mode 100644 index 0000000000..b586211268 --- /dev/null +++ b/dspace/config/spring/api/wos-integration.xml @@ -0,0 +1,291 @@ + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + static_data/item/keywords_plus/keyword + static_data/fullrecord_metadata/keywords/keyword + static_data/fullrecord_metadata/category_info/headings/heading + static_data/fullrecord_metadata/category_info/subheadings/subheading + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From a829cb9fece5637d1103a8ffb22ac8b7e2033b22 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 1 Apr 2022 00:35:46 +0200 Subject: [PATCH 0811/1254] [CST-5303] fix failed test --- .../org/dspace/app/rest/ExternalSourcesRestControllerIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 9205c3b88e..8b39e2004e 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 @@ -41,6 +41,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems( ExternalSourceMatcher.matchExternalSource("mock", "mock", false), ExternalSourceMatcher.matchExternalSource("orcid", "orcid", false), + ExternalSourceMatcher.matchExternalSource("scopus", "scopus", false), ExternalSourceMatcher.matchExternalSource( "sherpaJournalIssn", "sherpaJournalIssn", false), ExternalSourceMatcher.matchExternalSource( @@ -52,7 +53,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati ExternalSourceMatcher.matchExternalSource( "openAIREFunding", "openAIREFunding", false) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(7))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(8))); } @Test From 6cecbbcdb9eb5ae2cc6f495d00956de2808cd676 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 1 Apr 2022 13:01:36 +0200 Subject: [PATCH 0812/1254] [CST-5303] porting of cinii live import service --- .../external/cinii/CiniiFieldMapping.java | 37 ++ .../CiniiImportMetadataSourceServiceImpl.java | 391 ++++++++++++++++++ .../spring-dspace-addon-import-services.xml | 5 + .../config/spring/api/cinii-integration.xml | 110 +++++ .../config/spring/api/external-services.xml | 11 + 5 files changed, 554 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java create mode 100644 dspace/config/spring/api/cinii-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiFieldMapping.java new file mode 100644 index 0000000000..ad010d6c75 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiFieldMapping.java @@ -0,0 +1,37 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.cinii; + +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 ArXiv metadatum fields on the DSpace metadatum fields + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + */ +public class CiniiFieldMapping 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 = "ciniiMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} 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 new file mode 100644 index 0000000000..39b345c552 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java @@ -0,0 +1,391 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.cinii; + +import java.io.IOException; +import java.io.StringReader; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; +import javax.el.MethodNotFoundException; + +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpException; +import org.apache.http.client.utils.URIBuilder; +import org.apache.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.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.scopus.service.LiveImportClient; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.components.QuerySource; +import org.dspace.services.ConfigurationService; +import org.jdom2.Attribute; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implements a data source for querying Cinii + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class CiniiImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService + implements QuerySource { + + private static final Logger log = Logger.getLogger(CiniiImportMetadataSourceServiceImpl.class); + + private static final String ENDPOINT_SEARCH = "https://ci.nii.ac.jp/naid/"; + + @Autowired + private LiveImportClient liveImportClient; + + @Autowired + private ConfigurationService configurationService; + + @Override + public String getImportSource() { + return "cinii"; + } + + @Override + public void init() throws Exception {} + + @Override + public ImportRecord getRecord(String id) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(id)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + return retry(new CountByQueryCallable(query)); + } + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query, count, start)); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + return retry(new SearchByQueryCallable(query)); + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = retry(new SearchByIdCallable(query)); + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + return retry(new FindMatchingRecordCallable(query)); + } + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for CrossRef"); + } + + + 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 records = new LinkedList(); + Integer count = query.getParameterAsClass("count", Integer.class); + Integer start = query.getParameterAsClass("start", Integer.class); + String queryString = query.getParameterAsClass("query", String.class); + String appId = configurationService.getProperty("submission.lookup.cinii.appid"); + List ids = getCiniiIds(appId, count, null, null, null, start, queryString); + if (ids != null && ids.size() > 0) { + for (String id : ids) { + List tmp = search(id, appId); + if (tmp != null) { + MetadatumDTO metadatumDto = new MetadatumDTO(); + metadatumDto.setSchema("dc"); + metadatumDto.setElement("identifier"); + metadatumDto.setQualifier("other"); + metadatumDto.setValue(id); + for (ImportRecord ir : tmp) { + ir.addValue(metadatumDto); + } + records.addAll(tmp); + } + } + } + return records; + } + } + + 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 { + String appId = configurationService.getProperty("submission.lookup.cinii.appid"); + String id = query.getParameterAsClass("id", String.class); + List importRecord = search(id, appId); + MetadatumDTO metadatumDto = new MetadatumDTO(); + metadatumDto.setSchema("dc"); + metadatumDto.setElement("identifier"); + metadatumDto.setQualifier("other"); + metadatumDto.setValue(id); + for (ImportRecord ir : importRecord) { + ir.addValue(metadatumDto); + } + return importRecord; + } + } + + private class FindMatchingRecordCallable implements Callable> { + + private Query query; + + private FindMatchingRecordCallable(Query q) { + query = q; + } + + @Override + public List call() throws Exception { + List records = new LinkedList(); + String title = query.getParameterAsClass("title", String.class); + String author = query.getParameterAsClass("author", String.class); + Integer year = query.getParameterAsClass("year", Integer.class); + Integer maxResult = query.getParameterAsClass("maxResult", Integer.class); + Integer start = query.getParameterAsClass("start", Integer.class); + String appId = configurationService.getProperty("submission.lookup.cinii.appid"); + List ids = getCiniiIds(appId, maxResult, author, title, year, start, null); + if (ids != null && ids.size() > 0) { + for (String id : ids) { + List tmp = search(id, appId); + if (tmp != null) { + MetadatumDTO metadatumDto = new MetadatumDTO(); + metadatumDto.setSchema("dc"); + metadatumDto.setElement("identifier"); + metadatumDto.setQualifier("other"); + metadatumDto.setValue(id); + for (ImportRecord ir : tmp) { + ir.addValue(metadatumDto); + } + records.addAll(tmp); + } + } + } + return records; + } + + } + + private class CountByQueryCallable implements Callable { + private Query query; + + + private CountByQueryCallable(String queryString) { + query = new Query(); + query.addParameter("query", queryString); + } + + private CountByQueryCallable(Query query) { + this.query = query; + } + + @Override + public Integer call() throws Exception { + String appId = configurationService.getProperty("submission.lookup.cinii.appid"); + String queryString = query.getParameterAsClass("query", String.class); + return countCiniiElement(appId, null, null, null, null, null, queryString); + } + } + + + /** + * Get metadata by searching CiNii RDF API with CiNii NAID + * + * @param id CiNii NAID to search by + * @param appId registered application identifier for the API + * @return record metadata + * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. + * @throws HttpException Represents a XML/HTTP fault and provides access to the HTTP status code. + */ + protected List search(String id, String appId) + throws IOException, HttpException { + try { + List records = new LinkedList(); + URIBuilder uriBuilder = new URIBuilder(ENDPOINT_SEARCH + id + ".rdf?appid=" + appId); + String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + List elements = splitToRecords(response); + for (Element record : elements) { + records.add(transformSourceRecords(record)); + } + return records; + } catch (URISyntaxException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + } + + private List splitToRecords(String recordsSrc) { + try { + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(recordsSrc)); + Element root = document.getRootElement(); + List namespaces = Arrays + .asList(Namespace.getNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")); + XPathExpression xpath = XPathFactory.instance().compile("rdf:Description", Filters.element(), + null, namespaces); + Element record = xpath.evaluateFirst(root); + return Arrays.asList(record); + } catch (JDOMException | IOException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + } + + private List getCiniiIds(String appId, Integer maxResult, String author, String title, + Integer year, Integer start, String query) { + try { + List ids = new ArrayList<>(); + URIBuilder uriBuilder = new URIBuilder("https://ci.nii.ac.jp/opensearch/search"); + uriBuilder.addParameter("format", "rss"); + if (StringUtils.isNotBlank(appId)) { + uriBuilder.addParameter("appid", appId); + } + if (maxResult != null && maxResult != 0) { + uriBuilder.addParameter("count", maxResult.toString()); + } + if (start != null) { + uriBuilder.addParameter("start", start.toString()); + } + if (title != null) { + uriBuilder.addParameter("title", title); + } + if (author != null) { + uriBuilder.addParameter("author", author); + } + if (query != null) { + uriBuilder.addParameter("q", query); + } + if (year != null && year != -1 && year != 0) { + uriBuilder.addParameter("year_from", String.valueOf(year)); + uriBuilder.addParameter("year_to", String.valueOf(year)); + } + String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + int url_len = "http://ci.nii.ac.jp/naid/".length(); + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(response)); + Element root = document.getRootElement(); + List namespaces = Arrays.asList( + Namespace.getNamespace("ns", "http://purl.org/rss/1.0/"), + Namespace.getNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")); + XPathExpression xpath = XPathFactory.instance().compile("//ns:item/@rdf:about", + Filters.attribute(), null, namespaces); + List recordsList = xpath.evaluate(root); + for (Attribute item : recordsList) { + String value = item.getValue(); + if (value.length() > url_len) { + ids.add(value.substring(url_len + 1)); + } + } + return ids; + } catch (JDOMException | IOException | URISyntaxException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + } + + private Integer countCiniiElement(String appId, Integer maxResult, String author, String title, + Integer year, Integer start, String query) { + try { + URIBuilder uriBuilder = new URIBuilder("https://ci.nii.ac.jp/opensearch/search"); + uriBuilder.addParameter("format", "rss"); + uriBuilder.addParameter("appid", appId); + if (maxResult != null && maxResult != 0) { + uriBuilder.addParameter("count", maxResult.toString()); + } + if (start != null) { + uriBuilder.addParameter("start", start.toString()); + } + if (title != null) { + uriBuilder.addParameter("title", title); + } + if (author != null) { + uriBuilder.addParameter("author", author); + } + if (query != null) { + uriBuilder.addParameter("q", query); + } + if (year != null && year != -1 && year != 0) { + uriBuilder.addParameter("year_from", String.valueOf(year)); + uriBuilder.addParameter("year_to", String.valueOf(year)); + } + String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), + new HashMap()); + + SAXBuilder saxBuilder = new SAXBuilder(); + Document document = saxBuilder.build(new StringReader(response)); + Element root = document.getRootElement(); + List namespaces = Arrays + .asList(Namespace.getNamespace("opensearch", "http://a9.com/-/spec/opensearch/1.1/")); + XPathExpression xpath = XPathFactory.instance().compile("//opensearch:totalResults", + Filters.element(), null, namespaces); + List nodes = xpath.evaluate(root); + if (nodes != null && !nodes.isEmpty()) { + return Integer.parseInt(((Element) nodes.get(0)).getText()); + } + return 0; + } catch (JDOMException | IOException | URISyntaxException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + } + +} \ No newline at end of file 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 c9e901d89f..26849f88e1 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 @@ -134,6 +134,11 @@
    + + + + + diff --git a/dspace/config/spring/api/cinii-integration.xml b/dspace/config/spring/api/cinii-integration.xml new file mode 100644 index 0000000000..04bc267e36 --- /dev/null +++ b/dspace/config/spring/api/cinii-integration.xml @@ -0,0 +1,110 @@ + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 35c2381351..ea09a6d46c 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -116,5 +116,16 @@ + + + + + + + Publication + + + + From f5a1f2f9d86957804afc920d1a903a87b3effaa6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 1 Apr 2022 13:02:35 +0200 Subject: [PATCH 0813/1254] [CST-5303] added tests for cinii live import service --- .../dspaceFolder/assetstore/cinii-first.xml | 114 +++++++++++ .../assetstore/cinii-responce-ids.xml | 69 +++++++ .../dspaceFolder/assetstore/cinii-second.xml | 151 +++++++++++++++ .../CiniiImportMetadataSourceServiceIT.java | 177 ++++++++++++++++++ .../src/test/resources/test-config.properties | 5 +- 5 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/cinii-first.xml create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/cinii-responce-ids.xml create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/cinii-second.xml create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/CiniiImportMetadataSourceServiceIT.java diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-first.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-first.xml new file mode 100644 index 0000000000..f002f4b0bf --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-first.xml @@ -0,0 +1,114 @@ + + + + + + A Review of the Chinese Government Support and Sustainability Assessment for Ecovillage Development with a Global Perspective + Gao Xihong + Wang Fan + Liu Chenxi + Luo Tao + Zhang Yukun + Nuti Camillo + 空間計画と持続可能な開発国際学会 + International Review for Spatial Planning and Sustainable Development + 10 + 1 + 43 + 73 + 2022 + <p>Having achieved substantial progress in urban development over the past three decades, the Chinese government has turned to ecovillage development as one of the more effective ways to solve increasingly serious rural issues, such as poverty, rural hollowing, a deteriorating natural environment, and farmland abandonment. However, in spite of various promotional policies and substantial financial investment, there are very few studies assessing the impact of governmental support on ecovillage development. This paper presents a study applying both qualitative research and quantitative analysis to compare the effects of the support, especially in funding and policies, on their development. A comparison was made of three cases, one in China and two elsewhere. To provide a common basis for comparison, three quantification based assessments were examined with a view to applying them in this study.<b> </b>These were the Evaluation for Construction of Beautiful Village (ECBV) from China, and the BREEAM Community and LEED-ND, two well-established international examples. The following analyses using the three methods reveal the strengths and weaknesses of the Chinese government support in ecovillage development, and limitations of the quantification-based assessment methods. Proposals are made for improving the nature of government support and the use of the ECBV. These research outcomes can help formulate the rural development policies in the critical time of socio-economic transition in China, and the research process could be a reference to review quantification based assessment methods in other developing countries with similar levels of development. </p> + + + + + + 2022 + 130008141851 + ENG + J-STAGE + + 10.14246/irspsd.10.1_43 + + + + A Review of the Chinese Government Support and Sustainability Assessment for Ecovillage Development with a Global Perspective + Gao Xihong + Wang Fan + Liu Chenxi + Luo Tao + Zhang Yukun + Nuti Camillo + International Community of Spatial Planning and Sustainable Development + International Review for Spatial Planning and Sustainable Development + <p>Having achieved substantial progress in urban development over the past three decades, the Chinese government has turned to ecovillage development as one of the more effective ways to solve increasingly serious rural issues, such as poverty, rural hollowing, a deteriorating natural environment, and farmland abandonment. However, in spite of various promotional policies and substantial financial investment, there are very few studies assessing the impact of governmental support on ecovillage development. This paper presents a study applying both qualitative research and quantitative analysis to compare the effects of the support, especially in funding and policies, on their development. A comparison was made of three cases, one in China and two elsewhere. To provide a common basis for comparison, three quantification based assessments were examined with a view to applying them in this study.<b> </b>These were the Evaluation for Construction of Beautiful Village (ECBV) from China, and the BREEAM Community and LEED-ND, two well-established international examples. The following analyses using the three methods reveal the strengths and weaknesses of the Chinese government support in ecovillage development, and limitations of the quantification-based assessment methods. Proposals are made for improving the nature of government support and the use of the ECBV. These research outcomes can help formulate the rural development policies in the critical time of socio-economic transition in China, and the research process could be a reference to review quantification based assessment methods in other developing countries with similar levels of development. </p> + + + + + + + + + + + Gao Xihong + + + School of Architecture and Urban Planning, Fuzhou University|Department of Architecture, Roma Tre University + + + + + + + Wang Fan + + + School of Architecture and Urban Planning, Fuzhou University|Institute of Sustainable Building Design, School of Energy, Geoscience, Infrastructure and Society, Heriot-Watt University + + + + + + + Liu Chenxi + + + Institute of Sustainable Building Design, School of Energy, Geoscience, Infrastructure and Society, Heriot-Watt University + + + + + + + Luo Tao + + + School of Architecture and Urban Planning, Fuzhou University + + + + + + + Zhang Yukun + + + School of Architecture, Tianjin University + + + + + + + Nuti Camillo + + + Department of Architecture, Roma Tre University + + + + + + \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-responce-ids.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-responce-ids.xml new file mode 100644 index 0000000000..20db8bdba1 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-responce-ids.xml @@ -0,0 +1,69 @@ + + + + CiNii OpenSearch - roma + CiNii OpenSearch - roma + https://ci.nii.ac.jp/opensearch/search?format=rss&q=roma&count=2&start=1&REMOTE_ADDR2=93.45.72.79 + 2022-04-01T18:36:12+09:00 + 32 + 1 + 2 + + + + + + + + + A Review of the Chinese Government Support and Sustainability Assessment for Ecovillage Development with a Global Perspective + https://ci.nii.ac.jp/naid/130008141851 + + Gao Xihong + Wang Fan + Liu Chenxi + Luo Tao + Zhang Yukun + Nuti Camillo + 空間計画と持続可能な開発国際学会 + International Review for Spatial Planning and Sustainable Development + 10 + 1 + 43 + 73 + 2022 + <p>Having achieved substantial progress in urban development over the past three decades, the Chinese government has turned to ecovillage development as one of the more effective ways to solve increasingly serious rural issues, such as poverty, rural hollowing, a deteriorating natural environment, and farmland abandonment. However, in spite of various promotional policies and substantial financial investment, there are very few studies assessing the impact of governmental support on ecovillage development. This paper presents a study applying both qualitative research and quantitative analysis to compare the effects of the support, especially in funding and policies, on their development. A comparison was made of three cases, one in China and two elsewhere. To provide a common basis for comparison, three quantification based assessments were examined with a view to applying them in this study.<b> </b>These were the Evaluation for Construction of Beautiful Village (ECBV) from China, and the BREEAM Community and LEED-ND, two well-established international examples. The following analyses using the three methods reveal the strengths and weaknesses of the Chinese government support in ecovillage development, and limitations of the quantification-based assessment methods. Proposals are made for improving the nature of government support and the use of the ECBV. These research outcomes can help formulate the rural development policies in the critical time of socio-economic transition in China, and the research process could be a reference to review quantification based assessment methods in other developing countries with similar levels of development. </p> + 2022 + + + Surface Electronic States and Inclining Surfaces in MoTe2 Probed by Photoemission Spectromicroscopy + https://ci.nii.ac.jp/naid/210000159181 + + Matsumoto Ryoya + Saini Naurang L. + Giampietri Alessio + Kandyba Viktor + Barinov Alexey + Jha Rajveer + Higashinaka Ryuji + Matsuda Tatsuma D. + Aoki Yuji + Mizokawa Takashi + Physical Society of Japan + Journal of the Physical Society of Japan + 0031-9015 + 90 + 8 + 84704 + 84704 + 2021-08-15 + 2021-08-15 + + \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-second.xml b/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-second.xml new file mode 100644 index 0000000000..37f0df4729 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/cinii-second.xml @@ -0,0 +1,151 @@ + + + + + + Surface Electronic States and Inclining Surfaces in MoTe2 Probed by Photoemission Spectromicroscopy + Matsumoto Ryoya + Saini Naurang L. + Giampietri Alessio + Kandyba Viktor + Barinov Alexey + Jha Rajveer + Higashinaka Ryuji + Matsuda Tatsuma D. + Aoki Yuji + Mizokawa Takashi + Physical Society of Japan + Journal of the Physical Society of Japan + 0031-9015 + 90 + 8 + 84704 + 84704 + 2021-08-15 + + 2021-08-15 + 210000159181 + Crossref + + + + + Surface Electronic States and Inclining Surfaces in MoTe2 Probed by Photoemission Spectromicroscopy + Matsumoto Ryoya + Saini Naurang L. + Giampietri Alessio + Kandyba Viktor + Barinov Alexey + Jha Rajveer + Higashinaka Ryuji + Matsuda Tatsuma D. + Aoki Yuji + Mizokawa Takashi + Physical Society of Japan + Journal of the Physical Society of Japan + + + + + + + Matsumoto Ryoya + + + Department of Applied Physics, Waseda University, Shinjuku, Tokyo 169-8555, Japan + + + + + + + Saini Naurang L. + + + Dipartimento di Fisica, Universitá di Roma "La Sapienza" - Piazzale Aldo Moro 2, 00185 Roma, Italy + + + + + + + Giampietri Alessio + + + Sincrotrone Trieste S.C.p.A., Area Science Park, 34012 Basovizza, Trieste, Italy + + + + + + + Kandyba Viktor + + + Sincrotrone Trieste S.C.p.A., Area Science Park, 34012 Basovizza, Trieste, Italy + + + + + + + Barinov Alexey + + + Sincrotrone Trieste S.C.p.A., Area Science Park, 34012 Basovizza, Trieste, Italy + + + + + + + Jha Rajveer + + + Department of Physics, Tokyo Metropolitan University, Hachioji, Tokyo 192-0397, Japan + + + + + + + Higashinaka Ryuji + + + Department of Physics, Tokyo Metropolitan University, Hachioji, Tokyo 192-0397, Japan + + + + + + + Matsuda Tatsuma D. + + + Department of Physics, Tokyo Metropolitan University, Hachioji, Tokyo 192-0397, Japan + + + + + + + Aoki Yuji + + + Department of Physics, Tokyo Metropolitan University, Hachioji, Tokyo 192-0397, Japan + + + + + + + Mizokawa Takashi + + + Department of Applied Physics, Waseda University, Shinjuku, Tokyo 169-8555, Japan + + + + + + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CiniiImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CiniiImportMetadataSourceServiceIT.java new file mode 100644 index 0000000000..ad58b250aa --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CiniiImportMetadataSourceServiceIT.java @@ -0,0 +1,177 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.dspace.importer.external.cinii.CiniiImportMetadataSourceServiceImpl; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.dspace.importer.external.scopus.service.LiveImportClientImpl; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link CiniiImportMetadataSourceServiceImpl} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class CiniiImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Autowired + private CiniiImportMetadataSourceServiceImpl ciniiServiceImpl; + + @Test + public void ciniiImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.ciniiIds").toString(); + String path2 = testProps.get("test.ciniiFirst").toString(); + String path3 = testProps.get("test.ciniiSecond").toString(); + FileInputStream ciniiIds = null; + FileInputStream ciniiFirst = null; + FileInputStream ciniiSecond = null; + try { + ciniiIds = new FileInputStream(path); + ciniiFirst = new FileInputStream(path2); + ciniiSecond = new FileInputStream(path3); + + String ciniiIdsXmlResp = IOUtils.toString(ciniiIds, Charset.defaultCharset()); + String ciniiFirstXmlResp = IOUtils.toString(ciniiFirst, Charset.defaultCharset()); + String ciniiSecondXmlResp = IOUtils.toString(ciniiSecond, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(ciniiIdsXmlResp, 200, "OK"); + CloseableHttpResponse response2 = mockResponse(ciniiFirstXmlResp, 200, "OK"); + CloseableHttpResponse response3 = mockResponse(ciniiSecondXmlResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response, response2, response3); + + context.restoreAuthSystemState(); + Collection collection2match = getRecords(); + Collection recordsImported = ciniiServiceImpl.getRecords("test query", 0, 2); + assertEquals(2, recordsImported.size()); + assertTrue(matchRecords(recordsImported, collection2match)); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + if (Objects.nonNull(ciniiIds)) { + ciniiIds.close(); + } + if (Objects.nonNull(ciniiFirst)) { + ciniiFirst.close(); + } + if (Objects.nonNull(ciniiSecond)) { + ciniiSecond.close(); + } + } + } + + @Test + public void ciniiImportMetadataGetRecordsCountTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + String path = testProps.get("test.ciniiIds").toString(); + try (FileInputStream file = new FileInputStream(path)) { + String ciniiXmlResp = IOUtils.toString(file, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(ciniiXmlResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + int tot = ciniiServiceImpl.getRecordsCount("test query"); + assertEquals(32, tot); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + private Collection getRecords() { + Collection records = new LinkedList(); + //define first record + List metadatums = new ArrayList(); + MetadatumDTO title = createMetadatumDTO("dc", "title", null, + "A Review of the Chinese Government Support and Sustainability" + + " Assessment for Ecovillage Development with a Global Perspective"); + MetadatumDTO source = createMetadatumDTO("dc", "source", null, + "International Review for Spatial Planning and Sustainable Development"); + MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2022"); + MetadatumDTO language = createMetadatumDTO("dc", "language", "iso", "ENG"); + MetadatumDTO identifier = createMetadatumDTO("dc", "identifier", "other", "130008141851"); + MetadatumDTO description = createMetadatumDTO("dc", "description", "abstract", + "

    Having achieved substantial progress in urban development over the past three decades," + + " the Chinese government has turned to ecovillage development as one of the more effective" + + " ways to solve increasingly serious rural issues, such as poverty, rural hollowing," + + " a deteriorating natural environment, and farmland abandonment. However, in spite of" + + " various promotional policies and substantial financial investment, there are very few" + + " studies assessing the impact of governmental support on ecovillage development." + + " This paper presents a study applying both qualitative research and quantitative analysis" + + " to compare the effects of the support, especially in funding and policies, on their development." + + " A comparison was made of three cases, one in China and two elsewhere. To provide a common basis" + + " for comparison, three quantification based assessments were examined with a view to applying" + + " them in this study. These were the Evaluation for Construction of Beautiful Village" + + " (ECBV) from China, and the BREEAM Community and LEED-ND, two well-established international" + + " examples. The following analyses using the three methods reveal the strengths and weaknesses" + + " of the Chinese government support in ecovillage development, and limitations of the" + + " quantification-based assessment methods. Proposals are made for improving the nature of" + + " government support and the use of the ECBV. These research outcomes can help formulate" + + " the rural development policies in the critical time of socio-economic transition in China," + + " and the research process could be a reference to review quantification based assessment" + + " methods in other developing countries with similar levels of development.

    "); + + metadatums.add(title); + metadatums.add(source); + metadatums.add(date); + metadatums.add(language); + metadatums.add(identifier); + metadatums.add(description); + + ImportRecord firstrRecord = new ImportRecord(metadatums); + + //define second record + List metadatums2 = new ArrayList(); + MetadatumDTO title2 = createMetadatumDTO("dc", "title", null, + "Surface Electronic States and Inclining Surfaces in MoTe2 Probed by Photoemission Spectromicroscopy"); + MetadatumDTO source2 = createMetadatumDTO("dc", "source", null, + "Journal of the Physical Society of Japan"); + MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2021-08-15"); + MetadatumDTO issn = createMetadatumDTO("dc", "identifier", "issn", "0031-9015"); + MetadatumDTO identifier2 = createMetadatumDTO("dc", "identifier", "other", "210000159181"); + + metadatums2.add(title2); + metadatums2.add(source2); + metadatums2.add(date2); + metadatums2.add(issn); + metadatums2.add(identifier2); + + ImportRecord secondRecord = new ImportRecord(metadatums2); + records.add(firstrRecord); + records.add(secondRecord); + return records; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/resources/test-config.properties b/dspace-server-webapp/src/test/resources/test-config.properties index 8ff06d65f5..2f24a55d5e 100644 --- a/dspace-server-webapp/src/test/resources/test-config.properties +++ b/dspace-server-webapp/src/test/resources/test-config.properties @@ -18,4 +18,7 @@ test.scopus = ./target/testing/dspace/assetstore/scopus-ex.xml test.scopus-empty = ./target/testing/dspace/assetstore/scopus-empty-resp.xml test.pubmedeurope = ./target/testing/dspace/assetstore/pubmedeurope-test.xml test.pubmedeurope-empty = ./target/testing/dspace/assetstore/pubmedeurope-empty.xml -test.wos = ./target/testing/dspace/assetstore/wos-responce.xml \ No newline at end of file +test.wos = ./target/testing/dspace/assetstore/wos-responce.xml +test.ciniiIds = ./target/testing/dspace/assetstore/cinii-responce-ids.xml +test.ciniiFirst = ./target/testing/dspace/assetstore/cinii-first.xml +test.ciniiSecond = ./target/testing/dspace/assetstore/cinii-second.xml \ No newline at end of file From c7a8e1b3b54ef5654b049b965f424a856f3e5d4e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 23 Mar 2022 11:52:14 -0500 Subject: [PATCH 0814/1254] Update to latest Solr to prove it doesn't break SWORDv2 --- dspace-rest/pom.xml | 11 ++++++++ pom.xml | 68 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 4433cffd33..5377dab901 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -102,6 +102,17 @@ com.fasterxml.jackson.module jackson-module-jaxb-annotations ${jackson.version} + + + + jakarta.activation + jakarta.activation-api + + + jakarta.xml.bind + jakarta.xml.bind-api + +
    diff --git a/pom.xml b/pom.xml index f050e1e36f..ff74fb8b6b 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 5.6.5.Final 6.0.23.Final 42.3.3 - 8.8.1 + 8.11.1 3.4.0 2.10.0 @@ -57,7 +57,7 @@ https://jena.apache.org/documentation/migrate_jena2_jena3.html --> 2.13.0 - 2.30.1 + 2.35 UTF-8 @@ -1106,6 +1106,13 @@ org.postgresql postgresql ${postgresql.driver.version} + + + + org.checkerframework + checker-qual + +
    + + + com.rometools + rome + + + org.tukaani + xz + com.google.protobuf protobuf-java - net.sf.ehcache - ehcache-core + com.healthmarketscience.jackcess + jackcess - jakarta.ws.rs - jakarta.ws.rs-api + com.healthmarketscience.jackcess + jackcess-encrypt - javax.xml.soap - javax.xml.soap-api + com.fasterxml.woodstox + woodstox-core + + + org.apache.james + apache-mime4j-dom + + + org.apache.commons + commons-compress + + + org.tallison + isoparser + + + org.tallison + metadata-extractor + + + com.epam + parso org.jvnet.staxex stax-ex + + + com.sun.activation + jakarta.activation + + + org.checkerframework + checker-qual + + xom From 84ffe9db9e7a91de2dc247cb25a9fa7c011c2498 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 28 Mar 2022 11:01:06 -0500 Subject: [PATCH 0815/1254] Upgrade Apache POI --- .../dspace/app/mediafilter/ExcelFilter.java | 2 +- .../dspace/app/mediafilter/PoiWordFilter.java | 6 ++-- .../app/mediafilter/PowerPointFilter.java | 28 +++++++++---------- pom.xml | 18 ++---------- 4 files changed, 18 insertions(+), 36 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ExcelFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ExcelFilter.java index c17d168c04..1ebd7b8715 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ExcelFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ExcelFilter.java @@ -12,8 +12,8 @@ import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.Logger; -import org.apache.poi.POITextExtractor; import org.apache.poi.extractor.ExtractorFactory; +import org.apache.poi.extractor.POITextExtractor; import org.apache.poi.hssf.extractor.ExcelExtractor; import org.apache.poi.xssf.extractor.XSSFExcelExtractor; import org.dspace.content.Item; diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java index 8c198c4477..942eafa04d 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java @@ -12,10 +12,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import org.apache.poi.POITextExtractor; import org.apache.poi.extractor.ExtractorFactory; -import org.apache.poi.openxml4j.exceptions.OpenXML4JException; -import org.apache.xmlbeans.XmlException; +import org.apache.poi.extractor.POITextExtractor; import org.dspace.content.Item; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +53,7 @@ public class PoiWordFilter // get input stream from bitstream, pass to filter, get string back POITextExtractor extractor = ExtractorFactory.createExtractor(source); text = extractor.getText(); - } catch (IOException | OpenXML4JException | XmlException e) { + } catch (IOException e) { System.err.format("Invalid File Format: %s%n", e.getMessage()); LOG.error("Unable to parse the bitstream: ", e); throw e; diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PowerPointFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PowerPointFilter.java index 86b7096f68..a825064353 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PowerPointFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/PowerPointFilter.java @@ -11,10 +11,10 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import org.apache.logging.log4j.Logger; -import org.apache.poi.POITextExtractor; import org.apache.poi.extractor.ExtractorFactory; -import org.apache.poi.hslf.extractor.PowerPointExtractor; -import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor; +import org.apache.poi.extractor.POITextExtractor; +import org.apache.poi.sl.extractor.SlideShowExtractor; +import org.apache.poi.xslf.extractor.XSLFExtractor; import org.dspace.content.Item; /* @@ -70,7 +70,6 @@ public class PowerPointFilter extends MediaFilter { try { String extractedText = null; - new ExtractorFactory(); POITextExtractor pptExtractor = ExtractorFactory .createExtractor(source); @@ -78,17 +77,16 @@ public class PowerPointFilter extends MediaFilter { // require different classes and APIs for text extraction // If this is a PowerPoint XML file, extract accordingly - if (pptExtractor instanceof XSLFPowerPointExtractor) { - - // The true method arguments indicate that text from - // the slides and the notes is desired - extractedText = ((XSLFPowerPointExtractor) pptExtractor) - .getText(true, true); - } else if (pptExtractor instanceof PowerPointExtractor) { // Legacy PowerPoint files - - extractedText = ((PowerPointExtractor) pptExtractor).getText() - + " " + ((PowerPointExtractor) pptExtractor).getNotes(); - + if (pptExtractor instanceof XSLFExtractor) { + // Extract text from both slides and notes + ((XSLFExtractor) pptExtractor).setNotesByDefault(true); + ((XSLFExtractor) pptExtractor).setSlidesByDefault(true); + extractedText = ((XSLFExtractor) pptExtractor).getText(); + } else if (pptExtractor instanceof SlideShowExtractor) { // Legacy PowerPoint files + // Extract text from both slides and notes + ((SlideShowExtractor) pptExtractor).setNotesByDefault(true); + ((SlideShowExtractor) pptExtractor).setSlidesByDefault(true); + extractedText = ((SlideShowExtractor) pptExtractor).getText(); } if (extractedText != null) { // if verbose flag is set, print out extracted text diff --git a/pom.xml b/pom.xml index ff74fb8b6b..fbdcd098d2 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.41.v20210516 2.17.1 2.0.24 - 3.17 + 5.2.2 1.18.0 1.7.25 @@ -1611,25 +1611,11 @@ org.apache.poi poi-ooxml ${poi-version} - - - - com.github.virtuald - curvesapi - - org.apache.poi - poi-ooxml-schemas + poi-ooxml-full ${poi-version} - - - - org.apache.xmlbeans - xmlbeans - - org.apache.xmlbeans From b894071c1666cf24be7281136a2f8453da643b20 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 28 Mar 2022 16:12:46 -0500 Subject: [PATCH 0816/1254] Switch to SolrJ & Solr-Core. We don't actually use Solr-Cell. Upgrade Tika dependencies --- dspace-api/pom.xml | 59 +++--------- dspace-server-webapp/pom.xml | 75 ++++++--------- dspace/modules/server/pom.xml | 25 ----- dspace/solr/search/conf/solrconfig.xml | 3 - pom.xml | 125 ++++++++----------------- 5 files changed, 81 insertions(+), 206 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index b8fae8d86c..009b18d0ac 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -394,7 +394,7 @@ org.ow2.asm asm-commons - + org.bouncycastle bcpkix-jdk15on @@ -606,27 +606,13 @@ httpmime + org.apache.solr solr-solrj ${solr.client.version} - - - - org.eclipse.jetty - jetty-http - - - org.eclipse.jetty - jetty-io - - - org.eclipse.jetty - jetty-util - - - + org.apache.solr @@ -654,39 +640,10 @@ - - org.apache.solr - solr-cell - - - - org.apache.commons - commons-text - - - - org.eclipse.jetty - jetty-http - - - org.eclipse.jetty - jetty-io - - - org.eclipse.jetty - jetty-util - - - org.apache.lucene lucene-core - - - org.apache.tika - tika-parsers - org.apache.lucene lucene-analyzers-icu @@ -707,6 +664,16 @@ xmlbeans + + + org.apache.tika + tika-core + + + org.apache.tika + tika-parsers-standard-package + + com.maxmind.geoip2 geoip2 diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index f90ea51b15..4677366a2c 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -371,13 +371,12 @@ dspace-services + + org.dspace dspace-iiif - - - org.dspace dspace-oai @@ -423,6 +422,32 @@ ${nimbus-jose-jwt.version} + + org.apache.solr + solr-solrj + ${solr.client.version} + + + + + org.springframework.boot + spring-boot-starter-cache + ${spring-boot.version} + + + + javax.cache + cache-api + 1.1.0 + + + + org.ehcache + ehcache + ${ehcache.version} + + org.springframework.boot @@ -495,50 +520,6 @@ - - - org.springframework.boot - spring-boot-starter-cache - ${spring-boot.version} - - - - javax.cache - cache-api - 1.1.0 - - - - org.ehcache - ehcache - ${ehcache.version} - - - org.apache.solr - solr-cell - test - - - - org.apache.commons - commons-text - - - - org.eclipse.jetty - jetty-http - - - org.eclipse.jetty - jetty-io - - - org.eclipse.jetty - jetty-util - - - org.apache.lucene lucene-analyzers-icu diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index c789cf5054..ad47074392 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -324,31 +324,6 @@ just adding new jar in the classloader mockito-inline test - - org.apache.solr - solr-cell - test - - - - org.apache.commons - commons-text - - - - org.eclipse.jetty - jetty-http - - - org.eclipse.jetty - jetty-io - - - org.eclipse.jetty - jetty-util - - - org.apache.lucene lucene-analyzers-icu diff --git a/dspace/solr/search/conf/solrconfig.xml b/dspace/solr/search/conf/solrconfig.xml index 204c1a056e..8d45105df5 100644 --- a/dspace/solr/search/conf/solrconfig.xml +++ b/dspace/solr/search/conf/solrconfig.xml @@ -30,9 +30,6 @@ regex='icu4j-.*\.jar'/> - - ${solr.data.dir:} diff --git a/pom.xml b/pom.xml index fbdcd098d2..c53a58d105 100644 --- a/pom.xml +++ b/pom.xml @@ -36,12 +36,15 @@ 2.3.1 1.1.0 - 9.4.41.v20210516 + 9.4.44.v20210927 2.17.1 2.0.24 5.2.2 1.18.0 1.7.25 + 2.3.0 + + 1.70 @@ -1115,28 +1118,6 @@ - - - org.apache.zookeeper - zookeeper - 3.4.14 - - - - - org.apache.james - apache-mime4j-core - 0.8.3 - - - - - org.ow2.asm - asm - 8.0.1 - - org.hibernate hibernate-core @@ -1293,7 +1274,7 @@ org.apache.solr - solr-cell + solr-solrj ${solr.client.version} @@ -1301,69 +1282,43 @@ lucene-core ${solr.client.version} - + + org.apache.tika - tika-parsers - 1.27 - - - - - com.rometools - rome - - - org.tukaani - xz - - - com.google.protobuf - protobuf-java - - - com.healthmarketscience.jackcess - jackcess - - - com.healthmarketscience.jackcess - jackcess-encrypt - - - com.fasterxml.woodstox - woodstox-core - - - org.apache.james - apache-mime4j-dom - - - org.apache.commons - commons-compress - - - org.tallison - isoparser - - - org.tallison - metadata-extractor - - - com.epam - parso - - - org.jvnet.staxex - stax-ex - - - - com.sun.activation - jakarta.activation - - + tika-core + ${tika.version} + + org.apache.tika + tika-parsers-standard-package + ${tika.version} + + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + + + + org.apache.james + apache-mime4j-core + 0.8.4 + + + + org.ow2.asm + asm + 8.0.1 + + @@ -1401,7 +1356,7 @@ org.slf4j slf4j-log4j12 - + org.apache.commons commons-csv From e49a1d86af4df9829b39e3775be99d24483d878c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 28 Mar 2022 16:13:38 -0500 Subject: [PATCH 0817/1254] Fix compilation issues. Old log4j v1 update to v2 --- .../org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java | 5 +++-- .../main/java/org/dspace/content/logic/DefaultFilter.java | 5 +++-- .../dspace/content/logic/condition/InCommunityCondition.java | 5 +++-- .../dspace/content/logic/condition/IsWithdrawnCondition.java | 5 +++-- .../content/logic/condition/MetadataValueMatchCondition.java | 5 +++-- .../logic/condition/MetadataValuesMatchCondition.java | 5 +++-- .../content/logic/condition/ReadableByGroupCondition.java | 5 +++-- .../java/org/dspace/google/GoogleAsyncEventListener.java | 5 +++-- 8 files changed, 24 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java b/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java index a1c3867fb9..6753a5d113 100644 --- a/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java +++ b/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java @@ -21,7 +21,8 @@ import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authority.AuthorityValue; import org.dspace.authority.SolrAuthorityInterface; import org.dspace.external.OrcidRestConnector; @@ -40,7 +41,7 @@ import org.orcid.jaxb.model.v3.release.search.Result; */ public class Orcidv3SolrAuthorityImpl implements SolrAuthorityInterface { - private static Logger log = Logger.getLogger(Orcidv3SolrAuthorityImpl.class); + private final static Logger log = LogManager.getLogger(); private OrcidRestConnector orcidRestConnector; private String OAUTHUrl; 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 c0649e9ea2..490c3949ea 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 @@ -7,7 +7,8 @@ */ package org.dspace.content.logic; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.core.Context; @@ -21,7 +22,7 @@ import org.dspace.core.Context; */ public class DefaultFilter implements Filter { private LogicalStatement statement; - private static Logger log = Logger.getLogger(Filter.class); + private final static Logger log = LogManager.getLogger(); /** * Set statement from Spring configuration in item-filters.xml 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 b9c1d15d2a..9f588f9c3b 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 @@ -10,7 +10,8 @@ package org.dspace.content.logic.condition; import java.sql.SQLException; import java.util.List; -import org.apache.log4j.Logger; +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; @@ -26,7 +27,7 @@ import org.dspace.core.Context; * @version $Revision$ */ public class InCommunityCondition extends AbstractCondition { - private static Logger log = Logger.getLogger(InCommunityCondition.class); + private final static Logger log = LogManager.getLogger(); /** * Return true if item is in one of the specified collections 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 6475ef09e2..6424e6f35f 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 @@ -7,7 +7,8 @@ */ package org.dspace.content.logic.condition; -import org.apache.log4j.Logger; +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; @@ -19,7 +20,7 @@ import org.dspace.core.Context; * @version $Revision$ */ public class IsWithdrawnCondition extends AbstractCondition { - private static Logger log = Logger.getLogger(IsWithdrawnCondition.class); + private final static Logger log = LogManager.getLogger(); /** * Return true if item is withdrawn 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 d9c774485a..4e30c75a2a 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 @@ -11,7 +11,8 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.logic.LogicalStatementException; @@ -26,7 +27,7 @@ import org.dspace.core.Context; */ public class MetadataValueMatchCondition extends AbstractCondition { - private static Logger log = Logger.getLogger(MetadataValueMatchCondition.class); + private final static Logger log = LogManager.getLogger(); /** * Return true if any value for a specified field in the item matches a specified regex pattern 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 df9cbfbf1d..74ccfa4ca8 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 @@ -11,7 +11,8 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.logic.LogicalStatementException; @@ -26,7 +27,7 @@ import org.dspace.core.Context; */ public class MetadataValuesMatchCondition extends AbstractCondition { - private static Logger log = Logger.getLogger(MetadataValuesMatchCondition.class); + private final static Logger log = LogManager.getLogger(); /** * Return true if any value for a specified field in the item matches any of the specified regex patterns 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 e76772803c..65f9925222 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 @@ -10,7 +10,8 @@ package org.dspace.content.logic.condition; import java.sql.SQLException; import java.util.List; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; @@ -27,7 +28,7 @@ import org.dspace.core.Context; * @version $Revision$ */ public class ReadableByGroupCondition extends AbstractCondition { - private static Logger log = Logger.getLogger(ReadableByGroupCondition.class); + private final static Logger log = LogManager.getLogger(); // Authorize service AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); diff --git a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java index cf5c40976d..9cca3777ef 100644 --- a/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java +++ b/dspace-api/src/main/java/org/dspace/google/GoogleAsyncEventListener.java @@ -31,7 +31,8 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -55,7 +56,7 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener { // 20 is the event max set by the GA API private static final int GA_MAX_EVENTS = 20; private static final String ANALYTICS_BATCH_ENDPOINT = "https://www.google-analytics.com/batch"; - private static Logger log = Logger.getLogger(GoogleAsyncEventListener.class); + private final static Logger log = LogManager.getLogger(); private static String analyticsKey; private static CloseableHttpClient httpclient; private static Buffer buffer; From a0438e51addb61aef44d3ced9b11ad22ea600896 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Mar 2022 09:20:37 -0500 Subject: [PATCH 0818/1254] Remove old version of XMLBeans. We don't use it directly and Tika brings in a new version --- dspace-api/pom.xml | 4 ---- dspace/modules/additions/pom.xml | 4 ---- pom.xml | 5 ----- 3 files changed, 13 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 009b18d0ac..eb0004b3ef 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -659,10 +659,6 @@ lucene-analyzers-stempel test - - org.apache.xmlbeans - xmlbeans - diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 9df5c8ef02..2abc12b529 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -273,10 +273,6 @@ lucene-analyzers-stempel test - - org.apache.xmlbeans - xmlbeans - junit diff --git a/pom.xml b/pom.xml index c53a58d105..9442ef1889 100644 --- a/pom.xml +++ b/pom.xml @@ -1572,11 +1572,6 @@ poi-ooxml-full ${poi-version} - - org.apache.xmlbeans - xmlbeans - 3.1.0 - xalan xalan From 508883bec8f1a16512abdd3a120665db1d61ede7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 28 Mar 2022 13:10:30 -0500 Subject: [PATCH 0819/1254] Replace format specific POI filters with TikaTextExtractionFilter --- dspace-api/pom.xml | 4 - .../dspace/app/mediafilter/ExcelFilter.java | 99 ---------- .../dspace/app/mediafilter/HTMLFilter.java | 82 -------- .../org/dspace/app/mediafilter/PDFFilter.java | 137 ------------- .../dspace/app/mediafilter/PoiWordFilter.java | 70 ------- .../app/mediafilter/PowerPointFilter.java | 111 ----------- .../mediafilter/TikaTextExtractionFilter.java | 178 +++++++++++++++++ .../indexobject/IndexFactoryImpl.java | 1 - .../app/mediafilter/PoiWordFilterTest.java | 181 ------------------ .../TikaTextExtractionFilterTest.java | 177 +++++++++++++++++ .../org/dspace/app/mediafilter/test.html | 53 +++++ .../org/dspace/app/mediafilter/test.pdf | Bin 0 -> 56812 bytes .../org/dspace/app/mediafilter/test.txt | 13 ++ dspace/config/dspace.cfg | 54 +++--- pom.xml | 21 -- 15 files changed, 451 insertions(+), 730 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/app/mediafilter/ExcelFilter.java delete mode 100644 dspace-api/src/main/java/org/dspace/app/mediafilter/HTMLFilter.java delete mode 100644 dspace-api/src/main/java/org/dspace/app/mediafilter/PDFFilter.java delete mode 100644 dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java delete mode 100644 dspace-api/src/main/java/org/dspace/app/mediafilter/PowerPointFilter.java create mode 100644 dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java delete mode 100644 dspace-api/src/test/java/org/dspace/app/mediafilter/PoiWordFilterTest.java create mode 100644 dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.html create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.pdf create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.txt diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index eb0004b3ef..2c998572f4 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -530,10 +530,6 @@ org.apache.pdfbox fontbox - - org.apache.poi - poi-scratchpad - xalan xalan diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ExcelFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ExcelFilter.java deleted file mode 100644 index 1ebd7b8715..0000000000 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ExcelFilter.java +++ /dev/null @@ -1,99 +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.mediafilter; - -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -import org.apache.commons.io.IOUtils; -import org.apache.logging.log4j.Logger; -import org.apache.poi.extractor.ExtractorFactory; -import org.apache.poi.extractor.POITextExtractor; -import org.apache.poi.hssf.extractor.ExcelExtractor; -import org.apache.poi.xssf.extractor.XSSFExcelExtractor; -import org.dspace.content.Item; - -/* - * ExcelFilter - * - * Entries you must add to dspace.cfg: - * - * filter.plugins = blah, \ - * Excel Text Extractor - * - * plugin.named.org.dspace.app.mediafilter.FormatFilter = \ - * blah = blah, \ - * org.dspace.app.mediafilter.ExcelFilter = Excel Text Extractor - * - * #Configure each filter's input Formats - * filter.org.dspace.app.mediafilter.ExcelFilter.inputFormats = Microsoft Excel, Microsoft Excel XML - * - */ -public class ExcelFilter extends MediaFilter { - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ExcelFilter.class); - - public String getFilteredName(String oldFilename) { - return oldFilename + ".txt"; - } - - /** - * @return String bundle name - */ - public String getBundleName() { - return "TEXT"; - } - - /** - * @return String bitstream format - */ - public String getFormatString() { - return "Text"; - } - - /** - * @return String description - */ - public String getDescription() { - return "Extracted text"; - } - - /** - * @param item item - * @param source source input stream - * @param verbose verbose mode - * @return InputStream the resulting input stream - * @throws Exception if error - */ - @Override - public InputStream getDestinationStream(Item item, InputStream source, boolean verbose) - throws Exception { - String extractedText = null; - - try { - POITextExtractor theExtractor = ExtractorFactory.createExtractor(source); - if (theExtractor instanceof ExcelExtractor) { - // for xls file - extractedText = (theExtractor).getText(); - } else if (theExtractor instanceof XSSFExcelExtractor) { - // for xlsx file - extractedText = (theExtractor).getText(); - } - } catch (Exception e) { - log.error("Error filtering bitstream: " + e.getMessage(), e); - throw e; - } - - if (extractedText != null) { - // generate an input stream with the extracted text - return IOUtils.toInputStream(extractedText, StandardCharsets.UTF_8); - } - - return null; - } -} diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/HTMLFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/HTMLFilter.java deleted file mode 100644 index 5e10f2841d..0000000000 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/HTMLFilter.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.app.mediafilter; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import javax.swing.text.Document; -import javax.swing.text.html.HTMLEditorKit; - -import org.dspace.content.Item; - -/* - * - * to do: helpful error messages - can't find mediafilter.cfg - can't - * instantiate filter - bitstream format doesn't exist - * - */ -public class HTMLFilter extends MediaFilter { - - @Override - public String getFilteredName(String oldFilename) { - return oldFilename + ".txt"; - } - - /** - * @return String bundle name - */ - @Override - public String getBundleName() { - return "TEXT"; - } - - /** - * @return String bitstream format - */ - @Override - public String getFormatString() { - return "Text"; - } - - /** - * @return String description - */ - @Override - public String getDescription() { - return "Extracted text"; - } - - /** - * @param currentItem item - * @param source source input stream - * @param verbose verbose mode - * @return InputStream the resulting input stream - * @throws Exception if error - */ - @Override - public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) - throws Exception { - // try and read the document - set to ignore character set directive, - // assuming that the input stream is already set properly (I hope) - HTMLEditorKit kit = new HTMLEditorKit(); - Document doc = kit.createDefaultDocument(); - - doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE); - - kit.read(source, doc, 0); - - String extractedText = doc.getText(0, doc.getLength()); - - // generate an input stream with the extracted text - byte[] textBytes = extractedText.getBytes(StandardCharsets.UTF_8); - ByteArrayInputStream bais = new ByteArrayInputStream(textBytes); - - return bais; - } -} diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFFilter.java deleted file mode 100644 index c90d7c5a6e..0000000000 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFFilter.java +++ /dev/null @@ -1,137 +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.mediafilter; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; - -import org.apache.logging.log4j.Logger; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException; -import org.apache.pdfbox.text.PDFTextStripper; -import org.dspace.content.Item; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - -/* - * - * to do: helpful error messages - can't find mediafilter.cfg - can't - * instantiate filter - bitstream format doesn't exist - * - */ -public class PDFFilter extends MediaFilter { - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(PDFFilter.class); - - @Override - public String getFilteredName(String oldFilename) { - return oldFilename + ".txt"; - } - - /** - * @return String bundle name - */ - @Override - public String getBundleName() { - return "TEXT"; - } - - /** - * @return String bitstreamformat - */ - @Override - public String getFormatString() { - return "Text"; - } - - /** - * @return String description - */ - @Override - public String getDescription() { - return "Extracted text"; - } - - /** - * @param currentItem item - * @param source source input stream - * @param verbose verbose mode - * @return InputStream the resulting input stream - * @throws Exception if error - */ - @Override - public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) - throws Exception { - ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); - try { - boolean useTemporaryFile = configurationService.getBooleanProperty("pdffilter.largepdfs", false); - - // get input stream from bitstream - // pass to filter, get string back - PDFTextStripper pts = new PDFTextStripper(); - pts.setSortByPosition(true); - PDDocument pdfDoc = null; - Writer writer = null; - File tempTextFile = null; - ByteArrayOutputStream byteStream = null; - - if (useTemporaryFile) { - tempTextFile = File.createTempFile("dspacepdfextract" + source.hashCode(), ".txt"); - tempTextFile.deleteOnExit(); - writer = new OutputStreamWriter(new FileOutputStream(tempTextFile)); - } else { - byteStream = new ByteArrayOutputStream(); - writer = new OutputStreamWriter(byteStream); - } - - try { - pdfDoc = PDDocument.load(source); - pts.writeText(pdfDoc, writer); - } catch (InvalidPasswordException ex) { - log.error("PDF is encrypted. Cannot extract text (item: {})", - () -> currentItem.getHandle()); - return null; - } finally { - try { - if (pdfDoc != null) { - pdfDoc.close(); - } - } catch (Exception e) { - log.error("Error closing PDF file: " + e.getMessage(), e); - } - - try { - writer.close(); - } catch (Exception e) { - log.error("Error closing temporary extract file: " + e.getMessage(), e); - } - } - - if (useTemporaryFile) { - return new FileInputStream(tempTextFile); - } else { - byte[] bytes = byteStream.toByteArray(); - return new ByteArrayInputStream(bytes); - } - } catch (OutOfMemoryError oome) { - log.error("Error parsing PDF document " + oome.getMessage(), oome); - if (!configurationService.getBooleanProperty("pdffilter.skiponmemoryexception", false)) { - throw oome; - } - } - - return null; - } -} diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java deleted file mode 100644 index 942eafa04d..0000000000 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PoiWordFilter.java +++ /dev/null @@ -1,70 +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.mediafilter; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -import org.apache.poi.extractor.ExtractorFactory; -import org.apache.poi.extractor.POITextExtractor; -import org.dspace.content.Item; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Extract flat text from Microsoft Word documents (.doc, .docx). - */ -public class PoiWordFilter - extends MediaFilter { - private static final Logger LOG = LoggerFactory.getLogger(PoiWordFilter.class); - - @Override - public String getFilteredName(String oldFilename) { - return oldFilename + ".txt"; - } - - @Override - public String getBundleName() { - return "TEXT"; - } - - @Override - public String getFormatString() { - return "Text"; - } - - @Override - public String getDescription() { - return "Extracted text"; - } - - @Override - public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) - throws Exception { - String text; - try { - // get input stream from bitstream, pass to filter, get string back - POITextExtractor extractor = ExtractorFactory.createExtractor(source); - text = extractor.getText(); - } catch (IOException e) { - System.err.format("Invalid File Format: %s%n", e.getMessage()); - LOG.error("Unable to parse the bitstream: ", e); - throw e; - } - - // if verbose flag is set, print out extracted text to STDOUT - if (verbose) { - System.out.println(text); - } - - // return the extracted text as a stream. - return new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)); - } -} diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PowerPointFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PowerPointFilter.java deleted file mode 100644 index a825064353..0000000000 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PowerPointFilter.java +++ /dev/null @@ -1,111 +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.mediafilter; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import org.apache.logging.log4j.Logger; -import org.apache.poi.extractor.ExtractorFactory; -import org.apache.poi.extractor.POITextExtractor; -import org.apache.poi.sl.extractor.SlideShowExtractor; -import org.apache.poi.xslf.extractor.XSLFExtractor; -import org.dspace.content.Item; - -/* - * TODO: Allow user to configure extraction of only text or only notes - * - */ -public class PowerPointFilter extends MediaFilter { - - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(PowerPointFilter.class); - - @Override - public String getFilteredName(String oldFilename) { - return oldFilename + ".txt"; - } - - /** - * @return String bundle name - */ - @Override - public String getBundleName() { - return "TEXT"; - } - - /** - * @return String bitstream format - * - * TODO: Check that this is correct - */ - @Override - public String getFormatString() { - return "Text"; - } - - /** - * @return String description - */ - @Override - public String getDescription() { - return "Extracted text"; - } - - /** - * @param currentItem item - * @param source source input stream - * @param verbose verbose mode - * @return InputStream the resulting input stream - * @throws Exception if error - */ - @Override - public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) - throws Exception { - - try { - - String extractedText = null; - POITextExtractor pptExtractor = ExtractorFactory - .createExtractor(source); - - // PowerPoint XML files and legacy format PowerPoint files - // require different classes and APIs for text extraction - - // If this is a PowerPoint XML file, extract accordingly - if (pptExtractor instanceof XSLFExtractor) { - // Extract text from both slides and notes - ((XSLFExtractor) pptExtractor).setNotesByDefault(true); - ((XSLFExtractor) pptExtractor).setSlidesByDefault(true); - extractedText = ((XSLFExtractor) pptExtractor).getText(); - } else if (pptExtractor instanceof SlideShowExtractor) { // Legacy PowerPoint files - // Extract text from both slides and notes - ((SlideShowExtractor) pptExtractor).setNotesByDefault(true); - ((SlideShowExtractor) pptExtractor).setSlidesByDefault(true); - extractedText = ((SlideShowExtractor) pptExtractor).getText(); - } - if (extractedText != null) { - // if verbose flag is set, print out extracted text - // to STDOUT - if (verbose) { - System.out.println(extractedText); - } - - // generate an input stream with the extracted text - byte[] textBytes = extractedText.getBytes(); - ByteArrayInputStream bais = new ByteArrayInputStream(textBytes); - - return bais; - } - } catch (Exception e) { - log.error("Error filtering bitstream: " + e.getMessage(), e); - throw e; - } - - return null; - } -} diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java new file mode 100644 index 0000000000..909291e450 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java @@ -0,0 +1,178 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.mediafilter; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tika.Tika; +import org.apache.tika.exception.TikaException; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.parser.AutoDetectParser; +import org.apache.tika.sax.BodyContentHandler; +import org.apache.tika.sax.ContentHandlerDecorator; +import org.dspace.content.Item; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.xml.sax.SAXException; + +/** + * Text Extraction media filter which uses Apache Tika to extract text from a large number of file formats (including + * all Microsoft formats, PDF, HTML, Text, etc). For a more complete list of file formats supported by Tika see the + * Tika documentation: https://tika.apache.org/2.3.0/formats.html + */ +public class TikaTextExtractionFilter + extends MediaFilter { + private final static Logger log = LogManager.getLogger(); + + @Override + public String getFilteredName(String oldFilename) { + return oldFilename + ".txt"; + } + + @Override + public String getBundleName() { + return "TEXT"; + } + + @Override + public String getFormatString() { + return "Text"; + } + + @Override + public String getDescription() { + return "Extracted text"; + } + + @Override + public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose) + throws Exception { + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + boolean useTemporaryFile = configurationService.getBooleanProperty("textextractor.use-temp-file", false); + + if (useTemporaryFile) { + // Extract text out of source file using a temp file, returning results as InputStream + return extractUsingTempFile(source, verbose); + } + + // Not using temporary file. We'll use Tika's default in-memory parsing. + // Get maximum characters to extract. Default is 100,000 chars, which is also Tika's default setting. + String extractedText; + int maxChars = configurationService.getIntProperty("textextractor.max-chars", 100000); + try { + // Use Tika to extract text from input. Tika will automatically detect the file type. + Tika tika = new Tika(); + tika.setMaxStringLength(maxChars); // Tell Tika the maximum number of characters to extract + extractedText = tika.parseToString(source); + } catch (IOException e) { + System.err.println("Unable to extract text from bitstream in Item " + currentItem.getID().toString()); + e.printStackTrace(); + log.error("Unable to extract text from bitstream in Item {}", currentItem.getID().toString(), e); + throw e; + } catch (OutOfMemoryError oe) { + System.err.println("OutOfMemoryError occurred when extracting text from bitstream in Item " + + currentItem.getID().toString() + + "You may wish to enable 'textextractor.use-temp-file'."); + oe.printStackTrace(); + log.error("OutOfMemoryError occurred when extracting text from bitstream in Item {}. " + + "You may wish to enable 'textextractor.use-temp-file'.", currentItem.getID().toString(), oe); + throw oe; + } + + if (StringUtils.isNotEmpty(extractedText)) { + // if verbose flag is set, print out extracted text to STDOUT + if (verbose) { + System.out.println("(Verbose mode) Extracted text:"); + System.out.println(extractedText); + } + + // return the extracted text as a UTF-8 stream. + return new ByteArrayInputStream(extractedText.getBytes(StandardCharsets.UTF_8)); + } + return null; + } + + /** + * Extracts the text out of a given source InputStream, using a temporary file. This decreases the amount of memory + * necessary for text extraction, but can be slower as it requires writing extracted text to a temporary file. + * @param source source InputStream + * @param verbose verbose mode enabled/disabled + * @return InputStream for temporary file containing extracted text + * @throws IOException + * @throws SAXException + * @throws TikaException + */ + private InputStream extractUsingTempFile(InputStream source, boolean verbose) + throws IOException, TikaException, SAXException { + File tempExtractedTextFile = File.createTempFile("dspacetextextract" + source.hashCode(), ".txt"); + + if (verbose) { + System.out.println("(Verbose mode) Extracted text was written to temporary file at " + + tempExtractedTextFile.getAbsolutePath()); + } else { + tempExtractedTextFile.deleteOnExit(); + } + + // Open temp file for writing + try (FileWriter writer = new FileWriter(tempExtractedTextFile, StandardCharsets.UTF_8)) { + // Initialize a custom ContentHandlerDecorator which is a BodyContentHandler. + // This mimics the behavior of Tika().parseToString(), which only extracts text from the body of the file. + // This custom Handler writes any extracted text to the temp file. + ContentHandlerDecorator handler = new BodyContentHandler(new ContentHandlerDecorator() { + /** + * Write all extracted characters directly to the temp file. + */ + @Override + public void characters(char[] ch, int start, int length) { + try { + writer.append(new String(ch), start, length); + } catch (IOException e) { + log.error("Could not append to temporary file at {} when performing text extraction", + tempExtractedTextFile.getAbsolutePath(), e); + } + } + + /** + * Write all ignorable whitespace directly to the temp file. + * This mimics the behaviour of Tika().parseToString() which extracts ignorableWhitespace characters + * (like blank lines, indentations, etc.), so that we get the same extracted text either way. + */ + @Override + public void ignorableWhitespace(char[] ch, int start, int length) { + try { + writer.append(new String(ch), start, length); + } catch (IOException e) { + log.error("Could not append to temporary file at {} when performing text extraction", + tempExtractedTextFile.getAbsolutePath(), e); + } + } + }); + + AutoDetectParser parser = new AutoDetectParser(); + Metadata metadata = new Metadata(); + // parse our source InputStream using the above custom handler + parser.parse(source, handler, metadata); + } + + // At this point, all extracted text is written to our temp file. So, return a FileInputStream for that file + return new FileInputStream(tempExtractedTextFile); + } + + + + +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java index 8660bbebc7..3c93e1c522 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/IndexFactoryImpl.java @@ -95,7 +95,6 @@ public abstract class IndexFactoryImpl implements 100000); // Use Tika's Text parser as the streams are always from the TEXT bundle (i.e. already extracted text) - // TODO: We may wish to consider using Tika to extract the text in the future. TextAndCSVParser tikaParser = new TextAndCSVParser(); BodyContentHandler tikaHandler = new BodyContentHandler(charLimit); Metadata tikaMetadata = new Metadata(); diff --git a/dspace-api/src/test/java/org/dspace/app/mediafilter/PoiWordFilterTest.java b/dspace-api/src/test/java/org/dspace/app/mediafilter/PoiWordFilterTest.java deleted file mode 100644 index 4d2353a29a..0000000000 --- a/dspace-api/src/test/java/org/dspace/app/mediafilter/PoiWordFilterTest.java +++ /dev/null @@ -1,181 +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.mediafilter; - -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -import org.dspace.content.Item; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * Drive the POI-based MS Word filter. - * - * @author mwood - */ -public class PoiWordFilterTest { - - public PoiWordFilterTest() { - } - - @BeforeClass - public static void setUpClass() { - } - - @AfterClass - public static void tearDownClass() { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of getFilteredName method, of class PoiWordFilter. - */ -/* - @Test - public void testGetFilteredName() - { - System.out.println("getFilteredName"); - String oldFilename = ""; - PoiWordFilter instance = new PoiWordFilter(); - String expResult = ""; - String result = instance.getFilteredName(oldFilename); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getBundleName method, of class PoiWordFilter. - */ -/* - @Test - public void testGetBundleName() - { - System.out.println("getBundleName"); - PoiWordFilter instance = new PoiWordFilter(); - String expResult = ""; - String result = instance.getBundleName(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getFormatString method, of class PoiWordFilter. - */ -/* - @Test - public void testGetFormatString() - { - System.out.println("getFormatString"); - PoiWordFilter instance = new PoiWordFilter(); - String expResult = ""; - String result = instance.getFormatString(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getDescription method, of class PoiWordFilter. - */ -/* - @Test - public void testGetDescription() - { - System.out.println("getDescription"); - PoiWordFilter instance = new PoiWordFilter(); - String expResult = ""; - String result = instance.getDescription(); - assertEquals(expResult, result); - // TODO review the generated test code and remove the default call to fail. - fail("The test case is a prototype."); - } -*/ - - /** - * Test of getDestinationStream method, of class PoiWordFilter. - * Read a constant .doc document and examine the extracted text. - * - * @throws java.lang.Exception passed through. - */ - @Test - public void testGetDestinationStreamDoc() - throws Exception { - System.out.println("getDestinationStream"); - Item currentItem = null; - InputStream source; - boolean verbose = false; - PoiWordFilter instance = new PoiWordFilter(); - InputStream result; - - source = getClass().getResourceAsStream("wordtest.doc"); - result = instance.getDestinationStream(currentItem, source, verbose); - assertTrue("Known content was not found", readAll(result).contains("quick brown fox")); - } - - /** - * Test of getDestinationStream method, of class PoiWordFilter. - * Read a constant .docx document and examine the extracted text. - * - * @throws java.lang.Exception passed through. - */ - @Test - public void testGetDestinationStreamDocx() - throws Exception { - System.out.println("getDestinationStream"); - Item currentItem = null; - InputStream source; - boolean verbose = false; - PoiWordFilter instance = new PoiWordFilter(); - InputStream result; - - source = getClass().getResourceAsStream("wordtest.docx"); - result = instance.getDestinationStream(currentItem, source, verbose); - assertTrue("Known content was not found", readAll(result).contains("quick brown fox")); - } - - /** - * Read the entire content of a stream into a String. - * - * @param stream a stream of UTF-8 characters. - * @return complete content of {@link stream} - * @throws IOException - */ - private static String readAll(InputStream stream) - throws IOException { - if (null == stream) { - return null; - } - - byte[] bytes = new byte[stream.available()]; - StringBuilder resultSb = new StringBuilder(bytes.length / 2); // Guess: average 2 bytes per character - int howmany; - while ((howmany = stream.read(bytes)) > 0) { - resultSb.append(new String(bytes, 0, howmany, StandardCharsets.UTF_8)); - } - return resultSb.toString(); - } -} diff --git a/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java b/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java new file mode 100644 index 0000000000..5ebc85c956 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java @@ -0,0 +1,177 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in 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.mediafilter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.dspace.AbstractUnitTest; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Test; + +/** + * Drive the POI-based MS Word filter. + * + * @author mwood + */ +public class TikaTextExtractionFilterTest extends AbstractUnitTest { + + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + /** + * Test of getDestinationStream method using temp file for text extraction + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithUseTempFile() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + // Extract text from file with "use-temp-file=true" + configurationService.setProperty("textextractor.use-temp-file", "true"); + InputStream source = getClass().getResourceAsStream("test.pdf"); + InputStream result = instance.getDestinationStream(null, source, false); + String tempFileExtractedText = readAll(result); + + // Verify text extracted successfully + assertTrue("Known content was not found in .pdf", tempFileExtractedText.contains("quick brown fox")); + + // Now, extract text from same file using default, in-memory + configurationService.setProperty("textextractor.use-temp-file", "false"); + source = getClass().getResourceAsStream("test.pdf"); + result = instance.getDestinationStream(null, source, false); + String inMemoryExtractedText = readAll(result); + + // Verify the two results are equal + assertEquals("Extracted text via temp file is the same as in-memory.", + inMemoryExtractedText, tempFileExtractedText); + } + + /** + * Test of getDestinationStream method when max characters is less than file size + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithMaxChars() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + // Set "max-chars" to a small value of 100 chars, which is less than the text size of the file. + configurationService.setProperty("textextractor.max-chars", "100"); + InputStream source = getClass().getResourceAsStream("test.pdf"); + InputStream result = instance.getDestinationStream(null, source, false); + String extractedText = readAll(result); + + // Verify we have exactly the first 100 characters + assertEquals(100, extractedText.length()); + // Verify it has some text at the beginning of the file, but NOT text near the end + assertTrue("Known beginning content was found", extractedText.contains("This is a text.")); + assertFalse("Known ending content was not found", extractedText.contains("Emergency Broadcast System")); + } + + /** + * Test of getDestinationStream method using older Microsoft Word document. + * Read a constant .doc document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithDoc() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("wordtest.doc"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .doc", readAll(result).contains("quick brown fox")); + } + + /** + * Test of getDestinationStream method using newer Microsoft Word document. + * Read a constant .docx document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithDocx() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("wordtest.docx"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .docx", readAll(result).contains("quick brown fox")); + } + + /** + * Test of getDestinationStream method using a PDF document + * Read a constant .pdf document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithPDF() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.pdf"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .pdf", readAll(result).contains("quick brown fox")); + } + + /** + * Test of getDestinationStream method using an HTML document + * Read a constant .html document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithHTML() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.html"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .html", readAll(result).contains("quick brown fox")); + } + + /** + * Test of getDestinationStream method using a TXT document + * Read a constant .txt document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithTxt() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.txt"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .txt", readAll(result).contains("quick brown fox")); + } + + /** + * Read the entire content of a stream into a String. + * + * @param stream a stream of UTF-8 characters. + * @return complete content of stream as a String + * @throws IOException + */ + private static String readAll(InputStream stream) + throws IOException { + return IOUtils.toString(stream, StandardCharsets.UTF_8); + } +} diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.html b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.html new file mode 100644 index 0000000000..7655f566cc --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.html @@ -0,0 +1,53 @@ + + + + +A Text Extraction Test Document for DSpace + + + + +
    + +

    A Text Extraction Test Document

    + +

    for

    + +

    DSpace

    + +

    + +

    This is a text. For the next sixty seconds this software +will conduct a test of the DSpace text extraction facility. This is only a +text.

    + +

    This is a paragraph that followed the first that lived in +the document that Jack built.

    + +

    Lorem ipsum dolor sit amet. The quick brown fox jumped over +the lazy dog. Yow! Are we having fun yet?

    + +

    This has been a test of the DSpace text extraction system. +In the event of actual content you would care what is written here.

    + +
    + +
    + +
    + +
    + +

    Tip o’ the hat to the U.S. Emergency Broadcast System for the format that I have +irreverently borrowed.

    + +
    + + + + + + diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.pdf b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5b3749cbff73a41baf8aa06b7f62339886bc6ca2 GIT binary patch literal 56812 zcmdSAWmH|=wk=2?K!Ow8-GXo2-QC^UIBXn(TX1&`1b26LcMmSX-5rYL-0$9V+Iz36 ze$=0$HQJtQtuf~4W6ZH;?`sR0ys#)OBOMz&3|Za#A3O{rAp;@E&;lNYo10$wn=O!D z$iUIS3S>$zZ(s^^AOyT$R-~6UurZ|s+R&;fQ4!KBIodlJIV#%&fr=oIBOxQpUlSPv zdrJu$6VUrKe}+I~!gn(uFE70e(9yv7eNI9qj=zSg=0F#qy}Uip1ZWSmF?x@|%<$&| zFE2a{(8l;LmOoMd1JqwI^gNk{)lp2z(ZCV-eqBt7jgak6u=f!=A^V@v zyM??x$Vdt3s73!?QF>*dt0TRH^?S(#|MepDuNR3w_})ukY;GV3a@BfwWgui_1Q4=u z0CWi7^RaP!NA_NUf6ZUFwoiD2&gC~ z_&%?)fuX~@$-frlUs*a3GW>f%{!IK&K}5~19D(-qqE_!&2?LEl#z1;$ppB`c86hKp zmHCgCgQGprz#1OLHN8x2J!**^$xCd@JK3Ei;gj<-Nr$w zOT0dWYUJgH&4{99vU3X9BV>Dg;lZ>GGQq|4^joM9BcM35@rf9)^fYYJpw@aVCcpM0bz1drdfU8G6jA=#C+*NKP(v>OP83QJ{k^e zO036Hyh0Jb^606Q8xp4L6@%Pc67}EIp?wI!U3Bqyzxs0q7ars7_Skbdx1y1f7}Q+? z#*OrbuHf1WHj*5*T^2#LW#o5Kq^gGc0w~{e46E`?02KY3t=Pt7)06l|fU4h)&@Utz zLWx>Mk>iS=N}&(q`L6ub>op3R>{vVG2F+5Rq1&x-UpTiDdKUu=q&ZwF4aA_sZWujy z-DcsNLz@U1rub0FMAI$Cm#U81mrALc40KB?0jlNTi_&@p{pjFYP3@p+EPQ2&IlDA1 z6Ex)y1REelpHT4w+)#q+H+4g`p^m|RyitkzMj=mcG%eJ7_;T*US0U?&4^o=yv8V!X zgO^_7XZ-6**B$*rn$bgOdR1I@8J6JzIr z$Le;hD7c7U@+BbR1v5JDSFH=3S?NOE<7blE5~&s&L)HnmR>4kEyK>$(5$|PW>pN z=W0gx+bwskC!`zg$%6OwRhmK$UI-7d<%T=glf_grj@ zu8T{UO0H>RNiE<}W<~#_1`k$O2syLDmK~@=7ww^ddIzo2|KxDZQGx zjew1V`F|h(u@?q97}=ZKI)dy8S^u(tsJXp^qmY?_Js}g|op}Fi%m@IyyBj;2IS?}a z7X$qdUH)@fL&X1)>whyf zy`T)eEXdy4z>40;fRORuY4A_Q{vVx$1pd?Mj}-V1r~hn#_by=gf3@lo#_y#xcl<^x z{$I5;Hn4I0GnWG)<6q@NuMASLG5h$iGAXYw{O{79rywvGy)`{%7U?p7{sl z|4j@2)3!nq!ha(F>0H3SbjIJL-cKuo#3Y1e3~cHDflDu}_Kxp;?cOgceRFtUsXy}W zT`T;-@OQ!fr^5&t|B5ksHA1GpLg_ye_;)cUXKxI&e_wpcf1nC2K)~(^Eb#p$^Q2(`9CQBg8Kh+pZ`tZU8Vs6giL=oga4@GziT;V zb4M#6r2yd{-AVX<2iO}JIhuoP-j5v|357vMPJgRs^1i7EE7=+t{i&*elcO2Pp7M`4 zCsYH0jNeZQz01A#jfe8}4NL~i9LxyJ9t;HL@cwTC<_J~}MhK?%KDGxlejl0q9TEO- z>W+ng&fR=%Uf`NhJ-Ao2#{QqF_F0GB7 zjDY`N0sHqV0{*=l|Mrlap@ov8HN7k$BM1H8z4yJDncheLC$Y@T0{AoGKXmb~m6c^I zxB21PFdhjUSAOh{Y$H;B2hL2(V+-Og3N7|9Pm$->7qFw4y?N$^FE>k39s%jUP1pPo zC5gx;6Q6`C1<&w_#jb?}!tbNjO7oivt50%ZN=Sd`H!&6@4pE3!H)Jj%Oa5q8+=VHQ z2K-7m20Q$*DKSftk{tvknFpgRo2+CHVEL}C8_GdVN+V3QYxr;(kPWwLfLCs5)w>Gw zn;z+k7BjM`nUn7cHUyh~YMacYl@yD>L&p44xB`qOeY-r}WG@L-a2B3{CHGPtPHhy(ir( z8WpyhS3TIJU_8$!vU{D78unQiMbcP6x3y&@=pz~1ZYlR`iclC|*KfTriq|82YBFCc za{`DIjYdNy3mXwUS97LwA6K-~p91lZdzDlZwS_QXcp6UgPJAgSwH7#g$CxYpcj>o{ zoI4@6!bb}QdiFIT+v`mAMX+Uu!i?C-AWA@6vC0Lqo^|*&SJ4^y#Iz*C071Fn0O4BX zfYZ+sf@nW^oSWvgis?-*Y4dk-6F4l`aHbVQtVMrcut5&NNJt?3M&RQD5Z?*o2e0Vj z$;;V2`NuXN-xhiXdkLq$)Zh)EHZ&dc>$DfH+0>kIZTl(Lx z!M`h;|MMC!voXAjiGMxJGQN`*fSvU(as1Ccdh7}75#Kj=my%)%l3;D|U19qr#hM12 z799;6T^UQE=IaxXW{fN?*r-Q}$-jvfqVmxn=If1a*H_8D`7u#P0qUuR7L}^91<0&ajrsJbbHJ^Pkk^4v#%tm^tM98Q{ z^0j4q(S4Se+H1>lj)a}i6s+-)y>mV1U`%&$U&$3lyoirft~ht;bq*V3h>H2dQMWTY zjBi@$5v8Y4oTSN?Zl3*`tQ{gpIh;Q)J>s%^e8BXlAgvI1`bB9yo zJcmK2F~8<~HdnEOJTmBWKb~iuC}Ux4T%0T#Lm45n5a#YuFHTs6woBKX#7G(4w!a^)r)5 z++RSa$VohoFVRK}sHQj0yZyBL5=t(k2VU4Urzba4=4jQ|&{3=FDhU9)s39|*{J12n zI!qEdAo*2CCie;T#a{e7rKy&{E@KMxKZfo(kqmzg>BV(!f_2kQ4T#AJVpw?qU(QuyRc#4{YmOT1f;$EZ^Z z7=>U#)NG{hl+SOUtxR3d_?83DU^$mNqRReWIG^|^y4fb0m5}{p^4Cn5=)XPw*Ws&O z7PrJzdlzafjGHm<-FS%9p!F*gGA>L%!Mo$?<{kOFY}{hfN=_n*jqFYe_|hEZZfqcpJ(Xa8glFD?wkHp7+4GXGt` zQkwz4O>)2NHfL_1&k@Gm!Ja?ZvEjpsd}2|YQ^ouvpTFgVtCk8E!TUTz&i!6@TkGZ6 zb)!ZN;dQ?zgW!kI^@4ZiR>y~j0VLIF-b1DefSzCI<4gk9UJM_(9XUTG>MBg%bwrmg&Q%~ZL#MIbyc2{}7xi6r| zun0yhc()8<_8dPq7oifS=oxHz`xT>p)ZywD`0#or%(4#@>hPvVAJfZHQ?IOK&{D5n zC6yw~iVK)i<_<{o418WDgxHQRkX=FBM zjbrJTjX(1D6hp4wlsTD|Q?TaG>u|BZ?i*0$q$F-q-6Zg&hnrxc ziMuS!K~JwZP5b$r&o+#GW|pxiuc&F#e(m-HE3tN&K!|Q3*T1m%s$AlPGPwW+r^z<{ z-JZF2=$F#uf~WPMWQ|svox_U5k_jucaGKt2%!WvZlW#mrkfI#M)P2^{F1)Fvb#@D{ z(@!PzmKHX4h-C_vOe&|L*)uVm52xm4CpP>83*Fx%l+yRZD(={z7u_894?e^JIU#; zrsZ?aj3=U^U#p`K+nj_nz!`6inwsqXh&AVs(b>hO$h@SELvI=&#swdDrl2u*Hj*X* z0$fO)8lnQQJ169+8KAGK81##+PwG|uKnXVxoKbTp$FHFbZ>K$(nPcWS8~Z%-72+nq z_;eWNLAhNBU6B4D5*A^g<|w5VZdt+zEUfBx*UnV~C%;b5*^le>57c2B9!XNNXZg{R zH42{`Wh^4&GnkjrN$UoW5YJVq4!Ec!Qpw45D98$m8=8>^5G}O?6g!h04vnhF5zF=D zvGj-DB3x~R*Vy}3+08|3YT8?zIvXOCCccXNCXpM0vZo>T>46A^{(*fYx-au%iU#X` zpBw)4@q3w3<*dGuzO8jlrJXnFfVH3{%mvq#@<{b0F2%AUw;S#2UI3iv$I5<#@j#zC zCu!1~fky518bwME;rZ#o0}BWv7-hE!x+u;PkHl|wtKd-79IbYT;isp`HOBCZrOhok zE8~Hu^QZOljh6Mx=1wkW$Ya?}EjjHszpsP3-PwMD!!JBC21AxsS9a2pt*$z_iM!^r zLMv+_&HzX(*d8EI+Pqx!>laS!*(O9NcoIv8ZJSNIh5ZV@A7Y5-st73}O_Wuo6o;lc84p|DP>@)V|K6Hl-BHkRBa zW?hhLfN6wde2!D@mhdFLBJr1-+%m+-Q<&(O8dj* zspk|9&2;-Vk%wUXYA~8}FmEDFR7U6hSt%@!k&X_r@|JGRXl3vxkNxt0J7sKj*%vvB z3hmPa&dm$N7_3Dd&dda#G+`pJKihW%Ae)wdw04gv;a`?oz5q{y{+eW4f>W}@Qy!GL zH%B>RcE|4Wf@>z;i<6D3+LZ3_tT;lOBQ$>grW%s*lk|?K?84@#Fpi8>UhqTaY3FlI z7_2NB4(ZnSLo93!!eCQcRvllF`pz#Mckz;1S(f!RI0D*KBu$DX9_78NG{+7QIw#il z5k^xDEnS$Ui$9Ba0FWaVR^{!5=3-f=cA5z)I+?A3eKU;lm-o@ug=U0oXnLD%U zGc<@xt&-39fLG{%zjkxV+QgqqylvjC3pqW{1L^b`WwQYiZn0{&Zp>+92QL~0=fcYv zgpH9!HTI#}$)%+Q0*>4bJ1czG0sJw&Dt2RL)2U%BVIcb3B`SdJGBBVi)y3Gd-mPH- z5$$x!UgM)@GT9EBw4}Bvnq{#!Q8Kn+5G0Qz%S_x zs)^5Ll-XvN;v%yPyd1uHYd2#KK%* ze*nk9)e2sac7l^Z^Zw13Cv6|h7u>_X&egk_#XE!iCG#$nD6;sF)Gk=Fub@`OLmb)% zMx(2WSPk-+pBHZS`%~{z+szG`#iv;=<(#h8=U5&^BjopG!6LAY8kR?L3(jS1aULglir+yX zf=7X&X_B87ICYM72E8MV=JXNi42dOi4$-M}fIifUM*ooXfeix9_4dO##)$GUYM3po zU9e@JswDU-sm=6$Th>7qAJjU68{2TK4G!h{hp#&gi02?74y^0>4{osctdnU9iPu38 zJ1T~Gs-QlwRXR?8=b*YJgeDYqIMhLF5gTgl1{K}fXn9j$Eq6mAYA_vFk%NYCfxI?U#z>X}mLr|NTBr!c<57HrVyq}voObC zHXpuXTEpJU+7VSkdYStwU<@x4C<+e2tVBHv$m2<(?`ve4epmo6htwAQN+#2(sz2bP z>@8_u9XZQKOxCF_zTFiuK#54qL+sFLN137k$T1F__wvF;*NoxxmEr$+fdIaP0XrZ! z6bk*jjK7VXFZ&=pzNngxuN?jN?M@9p@A8j{`rk}*YAQO}CPY}wW|NO&+XUDhpy!h9 zgv5)H{t6s=(SUBxHUW@bh-eLW>?uNiUQ&3aa?}=LS2cR>vJbS6AFgsd)!c4S$rwt>*u$JjZK5C@;EI%XZY16_KX1?6+&6U} z;C%$}J^;K80bgTAT33L)17t@)eU0RA*@^wb@vEhI+qKMM5l+dOPRRvaqa?Vy7ECQ+ zKV6Ea6k8IPjECD@7Zpv3W{i$ui-n|U_D5VcBFd4`5gv;L9{t-L1Y|#Yy7)em?Y9hF zus2RQPZ^h=jA;GQvp1MJhujiqnE`2PY84VUv7Wnd7lki|qc518Y&TuCub6 zVcE9bAl4xM=d8S$Ej9oiD^PfwFlP=E6wcI-86n!Hz-Ui`g(9@+U##XOmsHpwnXP7A zZ1#;hi7Lt?Vjv~P=;k9-nCKdNULYhCi{kcdYUd}A(ICkubI{h{-Erg7KRHwzChrHqpcvJo{Iz7+-fq>POdhbt^a@vX&Q z>ntYs1409v&%`yM70=AYZ+?6(N@4L$!zo~f9wBfgtm!A9oVwg(xQy_1*uju+zqfBM zZMQ)@i|$=-4Jz9lIiir_)l%By%M;aTg|PS?CtQ4^n5djXGhb2^u`r8Np>gBHqoIME zSFosF6u|B#sgz1!PK~3JRYoq6)qfISR+lh#q6qJcY(8plVr17#qiK}LK8-yREy;tA zs0_ocm}eRQ4a4&TIn{N+ylNSRaX=2gi~#Y<@`b@!lY&0mUmtUFQ*__;Ery|C_e|cT z&H8NIJV|e_uK8#WTni#il~z1KjmLB%-37XRZ^S%fwVhv4+j-4tq6~J_Zh^ynX{keK zg|5%~v>D%`B6DeFrhL2%w!X;v=Cq#E`^@vm0%zjzC{@XGuKX#oe}1Un0;qS#;uX}W z5PBBp$uiuI{eE0@XYR=&TW$~`GsYuYe6cVdKB@|dOJ_kKJ&&d~e^`Xzi$u+E{Zzgu3T1aD8 zcUL2^!72za#WTgk2--r(8`jw=8ZKG-iP{v#E%Hr-3pHqY)9yEiGbJ_4`2uUnyl1&CC8KboUii>;%v)S2Hcwbnoc9^~npT#*!mwB`wFdL@O4wLTP9 z%#7-M4vnHISEquW{(MxeC4-kw7gw2@o^KduVF5Tu3~@57K|)X*xao6KSQ>pGTFmx|+`c8{ct1T&)2Mdy^g5FSSvxqP z*KK>yyrsGk!9z9s=5wVw@^ZKm3FPY2s;q1e$WF#f(_nRWvqg=#i-KMzw z(Ac%D%h(vA$@*`|oIufl`Am$T?T4|-llm&=(shtxPNMEH?VmiL@7*`_d^i<|C2RZD zMCY?Q;yByjdWca~2Xkjy{JzB)0Gw4F-P%YM=+t^ql-rJvRisND$ef(NWW#=x#BNYh z)uK9(@bO{xT)h!)zW5DhV%ES3(}BqS@j2I9`@YeJvFQopJ0i@(!i+bkk1&37O1%Or zoIqs$9u0sq+FHv4V`nH(b{Y7I~PNDeG(2vD~l&A#LraIqDz=-5M`Lfd6`qX^#L+JiGL5AgWq=xrS*8m3x3;7>H+<{ zCV7ko2DzKT*%K&$pCTXaLut2ROoeT3S2CxW%)A@}ZmAF=%4#L3l$v|O%u!GB7c@xh zQOxR2X=iM3-EFem)nSfc2|jsC%WXt6V9>t!uy$_|-dz05R&B^gj~CjT{5A`9;61r) z#ehRTYirX;?t#;IH-oV8(2u_kN&71DWUa2h|V52NXxvfg2E!z>t8?)yZmRJiGN73fr*sm}KM z50@NU;H+OTZaUCy;PmqFTCOXplVA$NMG3ujiBi#f+7Y-hW`K^6PBS;bNjT`X-#B1fAHY> zQRczt?e9$t5ax28qT_Z65$j!cJ))FUSMxIsw~0ITdXWm`?!oM_+NO^r;AQ+B@Yd`8 zGMl;PE$}w>B6sVJ%gfh_ed}Hr9oRh$UwO&eyMe%sNce@|r|AL4b-u|FMmQ55F>gW> zGCX8*-B)M2NLiy_A6Y+7FmuoRg5$QfV%737#XBW88*bc+w$al8meve~V zd9L4KjWW__y=A&Zbzyl?)|~*JCw@V-M}3t*ouHPm5Z@4Q#2Hks+raOVbV}7GqnDqV zub11Q@)rGy@?z(|HW{FF4-C}9=@vzFF4{Wqhof=NGu?(t*5z9u!`Ly%4}mj7%@xva%J)Rz=uCW;0u|qL7p4usM1MH1l<+83cijSlmRV~n4Hlev?@x!XlGmHIK z=m&jHAC)%WbwA09^Y0!gEY!eeqgo`dw~$lCdw1sb!N6cWYL}^??y-*Ex(kzIZ2B5I zN!E^NwMv~uJ2?g}Jd=?k|CeaxTg)d~*v-NYrIm^PEA~-UpR`|}R>*bH>258Ng}ls3 zMm8l%LkKo!dbb~6vT^U4UaqAydp=T&9;XL*HGiVkr=IBhp&GUNTdg!e@c{k!PV9*w ziL3sUc9p#M*J`#8xf0{p7QF)5e1DK@>Vd`ApKZZi5z3>Wype7lD}>vZ&x?Ep+OXPsuGvvkBsZ_Wq(b+~3NDxW#o3t10e^gz&0&C%80=LwrKvtQb|xKY&3sWvkfz16<^ zGpDoeKR=#{X&djP4Q@vcS!12Xqh~ORTzJ=JyR3Kr`fC z!AT>@n*Yp_Kl{%0mI-n;&-KSsK{<=NepA~SR|JYij03XtJ}-fajn@aMN3>J?vq_LG zt9ul}hNBbGUQJZ+$Xl3f5|MXjaLY<;ryIxt(KPCU!WNO~+*4qhis-es$IlQBP?M&R zs!*{E%|X$1In;|@14}h$`Lg8F?egQM5Y+vdK!pgz6KSkoRTdN!H77?n=quibw9%mMpv9WzK2+#4J+d;la6W!>W00c`y^CUFPzI{d1H^jesUe1_C8^rz zIbYXvH9B}bu5G)sLbH36of?N9rz#EWwVy=$N3ajAiifz98GH2O1mrDL&}P3#8qhj~ zrC(NW=8+9agg_A~wQ)(=FZ!jMcv>qc)Y(r>9v=SWXVrtFlB>YxYiFEuZ*6RruZAtz zDV*O%zhD;yiV8#S6yn3@@nqiau_I%^c+VW&#+%nxHim!i&b&5cM}^_5Fd>dh3?=m> zjpZ9)6w;Q&jT%yq5=gU)&0MlXI=IN{5*ENb?J;o(|3W{Ik^bXHEK-f~!$f>ueCL<> zWk7v1dYQzHTgMNlt)pOAsAt=5HL`+{k;Dn?d&Nh?Wt&hn_>gJXyaW0qaf09#CXWry z86s`GAEROjH>z;u`s2uH|uXR z9UmN8;gE5pa&qZs;S_JG1X+p_cF60BTl;$JQ}6+HGmq@%H8)2)818NM!$>Lu(*u$% z`tf_COiJGUentCdA=v8)ZSY8VZ$L&UO*3zcf9@5EogHMs$m)5gwvmj zoYV_L+fZ;(?VD6FLu=QM>44tfzvD^O9?W!q$S0=7ZI1(R4YsxYJ(-@q9S#0||EP6U zDTmwTt_mI_s@5SY$P_`VXML5k*F0(cWem=nQj}=7+Ke(RX?xuopc%@M#0Q0=p{=W` ztgXUb)tGk=m?58uY{WabTa@PLXLo3=B!|ry`N<&lyzzdE|5k-aa?q``VZgyPj5>}q z7e@)$VbQ2-ntN7%Ac)yT#dJe321t>QtVA2>q&_PM*ag#iGK)E(qhg%<4K!lmm@?u_ z9W^_MlEUy+ABacc||N8P>0!{BW(;8mTmls5Z~8wgucWx@kkj<HBdDq+JINjTzg@k+z& z`oqy}a*3NX#@xQWjfjX$YrVI?+c^hysE^Z0U_oX*c$KSi8snNFqn6PzxmcW3rZJY4 zjGTDGFMuM`4c&tfgj^G8Ti+NE`vzgojDfG)c~^Mx;Pw-jh!J!gbM~4cp0-JSZ}tVu z2)mTOQ>kb62q?uSBXQA6&o~97qpt@%QDp6P(PC9+gKO?@pRxLeyi^N zqkZ5tc1PssDusT@+vwZKW6BQ75ydTtc3r(=?0H^SznXcev1dFtj&jCkf}w9QYYvoGZqtrgOZixhBoSN;!^%d&#UL|GNH~V23yPRsXc$pkv~tk6t#JY1?V1-}7+wmHm_v+J9F|m3`w+|Qt`TxLE@U`DQ!-M1r5*Rm>Q3^M zF^$LrMV9t$luR`yw;_*WDXu2&DmFAIan>}7I&D04#^m?fC`zeuS^E2zxEfTQm7P^= zDwy*z3X+(SZ>z7Z+;uf$viG`w7-p(BGd|rOH%P&cOV(8vRcADWsp^!p&rNE!O0K7@ znIBbF44#ha#JMEAS}f1qj^*hj(4)60U2ELxRnI(&hV_`(12ypKjgwlK(*hLvcXY+- z2ZsqX?9-0D#kuca$^Ct?A0T+0SP;K1iQ6QII-ZgrxDhIgth6(f~inT9ZbNW8;tEaB(Tj&*X{o;%KuNUss+`{BLgNvvfCkF>&DXsaIMGKNFf_ z5x^)Hrq1Q* zytn4as}^(t6@ne5<280Fm!Vx!*C)2$<*ks$T(xpIe zU2t2u)g(jZP*Y(1(5yrCZ{=+w7McQWItnydNPnLw$6HsTa~Rz|OIMkSKCP6e?a$$; zX9;%Um3?`BSV5Aw2z`hH1zV2V@W|ocKlKZKH2Ucl>GbK75e>)S{HFhL*W*z=5>~EJ zz*uydyr}&tL>qUt#UpWa=v8OPY~RWUfh!8b(?L#k4F0r2FeyAi&oL?Mwc2{4+*$8b*Q-#SPgL$K! zwws*byFS$$!C>1?U5Wk3Y`eK@nQw#FGJShl$=9ldJfywepv#kJak>4qKHC0pOuZns z0l@S#Xe14tH6<||vNNv2U6DIh0l>0}+JrsJZMSS8Z$I@b`|4rf8wNG%JkuRfat)ts z-)WS=SEH&Nv1m-sv4gfg?5HG3)k|^*$^6_RNtaXrP}?C3Wb5OKT$>?q=bQ4^OiHt; z6|NJ4TTd7Hyj2xmJClCdDHC)3!K1k4Y=^q>u{wTxCqiv#Wz{AZ~;5G2xqVh%J4AI|Dh<(7E$6;RZ2+N_e+{0s#cNVsUlw2;WQ%Czcr%RpzAup;7F#t zFloy0!37X)DdRaTIM#|cVU;Ok9nz~*lVmL!7=OIQWg2Bh)U_grxQ-@eUi0?N0n{16 zgob7SSpjVp*nlFFJglles-0zRT?0^EuA+I#dJTWge${$pd*mABs)v1lbH!;8(0eMl zk5w05{e%z7} zA4$350+qvNKEfj+e`E;apFYxRb;OJrvgzocJjM<&LXxwe#-ZdSa$`~4&61sjo)*EjUin@Vt{KQA|=`Nlf z-6?4a3H@A+q*#qh?V}bHVqz(dtUxe{hcl87|J20*N!0^-EYjt>Rx?``X5Xn5B}r&{ zBRBHr+#9$^-i$2TvJ0ElDuV)DKHUpFE394OdIx3-kx0!2Uh2%q1p>tT8)dmj_zj8$ zO5|zfij>y7nvbkfqJiHqMf#T5vr%0eXC-=P#TXR|bOn=Z&$Q-wB}#E&fHgD?y} zA=BY+p{^glekm}+$MyAB{_fT_3iaka-D6RQTwN8D!wPP(EO66&DNI{{?TB_ zu0A{nBZ3}xN+CXl-#kLiHcp;c-Z8g3g8)bP5j%>Mhd@%ZMCh~7HASugVVCMpDL-+X zE1%|+V#hzv<>T(W3MuuU$X1r-~FL*#xma6t3?s+nHmhq z`BTuI)R-fo`l9VTmT3hsHiXJ6nRaAbaIpNZ=={Q}ar&&!$uDYtWo)r+3jNXx?m$@& zJPVO6_+(8quWmAFZJ1W2o$D{ED2>TXtr;#KJTHKKLi-W#XSVt$jBrIuxg(hkgi4_m ztTmez*xS&S2#AfeewVK8{>^!#GWsChFBDpDNRMT%%}$)x<@YwO#WxV=>pweime9_J zC##Ri8yRcqo>C@HHZl4#F)KnVutA|9>~ag;+(E?*&CHm*cul#XFBSbIDY0LrByt5r zs_^;wkb1xZMUYJo!Xir7k%UB5N4sg7@8osZakERg5q%6um0GK?E{xgJiqueVD&3^} z(wXzB4+Uz_>IGrE1C(s3Z4k%pG(Tw&9c_1Ce=QZZh5n}Mj4t4ldrWFB*3meqywbBU z?TimRX;jhzMj$DQO0#i?gUc?keEOx)bw=5BC`uWcT|f!5M!eBySO{=&BUXmPdLPdc1*mN-SZEkmbb>c9JbRoTLQtI{S}<>K6|F&8$=}Kz_xWWKb!shV`f_>s_6QC zp30zVH_orNDc_pQ+R7pmlfA0=R`eF=tI!MjAh9X9l22|=#!t%ePJb>&bI0QWZZGv} z&+Ja^R=3x3_`0}bP+G^+na*g09i}{LefarVm?WiRW)5~&3>%ZbGKww+V^#m88>auN zz1f;wHPJraq^q8!TObCcR8o|s6Swv>)N8D;F*;&tvb19TyFvG9lPOqEEqT0K zh!&ZuStil2$~604ocB4p1KBRLXUBzIyE$j7kF+$`VA~ez!LImwWWlrQ;U{LJn{S_h z>CZilRK`3L-wK)V3tc1K#$CN8YM?@9Z%RFs|5e_XTXIW;+*L{?(D_ez(<~jW8WQN^NOB%3=H0C21e@8` z3*3zwD+CO^Dhw62IS)M3L;c*9x~^KAs`ZUlg~e>9od_Umr6U7eFagJLcFQ2b<-5d? z$T{LCtQs<-+ds6!i^&g%goO;-$HR8;UvhV837U5E=?}uzzumT@dB1jbw?1z-+s4n- zHp8R%yLed(h}qvxRaB%u>iKgm3po{!*wA5k>~wP_j19u~YQ>N`JP|Et8MSpMY`F57Z{Bz!~b(wMAvf z63iRrT$2RPNeJMl{v1C4C@(*6#13-Yq~@0nz8OFmNM2-8hWWU+#&*JP0VUB2mo4Wl z`Af5$+;(rI@e^uV8g327Idu51)o-nxPov;uq)`%ouKiL$OdW{9ph+uXDmK^4d{tlVR>9Fz>navRb*E_tY)WWwCf zqTscd#8lv@R0X2fi8Y5S>`;c>9)eZmD3s5hG%AF$I!XrD6&uRsYKWyjydI)JSi_3i z9wT=w0XfuntF=!HCDBMdU+Sgse>|*?fViJ0F<4(CF4w*#E!M&)XvJRqDA7IVa$EkpxVJ<*)yO7~j#YHV|!-5}=ooMWC9 zo}#IKg>Ct5l5^z4$_v43zHFty>a^9K6N#zl98&c>NRFT40C6CxJXvruW`ZI7XGT=P zfITsDu7N8|-J2oQC8>}QdDcEGk5DOc;V>G0Fk_Cu&t$0SayEWfL-41`Hr>!S*n)#1oHm}Gk?C zB&3wXrVd~O=g7IaB1c-TIfIR?SkWb!9EdULCL`}Kt>{A?W;BYo9`u%@GWUuZ!zVc$ z%q|g%<~($CB_@Z7vqrA<Ai;k4N=)lij*HI80y$;Gmn*j%{kYia?XKQEk7*nA$mw8r7gC;FGlPQ(b@q0ThGfVxR(VG-Fb>*ut5vqQdoZ zqZ}=8@EiwwImmS_?)o29NjN+*qOU@_PDezJ>+L9N*HRtw=okKidE zPZ6J^>)EQOjnNK>{A{{ff2&TuFE1>N#4WxB?QQK8YvpY`#i9(ao?;93q5?coUj%r4 zZrK@u_3?$4Vg$7Fc?A~oc5g(8KvzB5bg-QfK+3{cCe|WDV8$Y9u|dXO z6Q3Uy4{<3A5q+(m(zc*FE5rsum#!xW1p}nMj4RFo63IMxfkD8J4pGa$($Kc3|8ueQ zFWz6jrlcjPY(mQ(J9NiY3nN}z$SpVmokKUYbii*m^(Wdct4$Ojpa2+t)Sh zy`jFosUy>xZ(g@QEnlwho_*==g?`T2+5W(iufuX@xM`qxML$ZJ=Pt~hrB7PE3WA^k zesGaHj>jyR0|d+l7QX|CHtheR1B(<)6%Gtke28=~Q#kPXPJF(@A%*}$S$#N__4rLI zI?W#dl4iB58!3+DAEnpzY63z${dIj(coDfhUK&PVW}=Vip?xe8}NQXdXf-h&m>5?^NyYovJPtk<6V!0zS%~U?;eDWyX@| za+cea#I5uW=8N<-v?nvF0>gGG0-~&>fNK1z zDYConLN0^61VzD(8>OcLfKn@bXnc_3tP-hyTj%kLV_0pyRuz9_#*#~FIgck;o5IAn zg$s8-^p#(K=e{R2T7!1gPPmQ4X8sZg;$VF zV9cpe6j#Q5NCTtJ&rjgE6>XtWAVEPwFPC1JCSW>+SF;@VlG({y8k_SVjx8K=uD4JT zUmvr^vWD&BHb@&H+>MqUmQgUOj!QpLPAgO3O^bB_Tn^WXYa{*f0dXL*LmY|R>;0;4 zzh}SrIDC{k8h;8t4}Z%2H2e;KHuA1=9*Qj0?OpG^PrgqXjh~Bi9tHkjZVD*qB_mn_ zQGjXQK-d{jM^y@_f~t_B`H;H*k~-Bn)v4YPJ>!P%p9VE6haT^>F@F&^X1lkD9@4J9 zEIVM2W1oX^WCcO-QN4BWP#vtRK!@o)$3nmgZ1wxR z7PhOt>m>EHYH46(c-3@yWO!z{e0G>XPuEM+)59b{ob}e+(6%PJC3;Jg{(2O#{_sZR zKke=9upJJUF|+^*448(2AQmOWt|xp&iy#ysP6`Fgzmvy>Vr@2l<3>0vJ?(Z|Rh6~| zE8M%|gI11?R`|%+AYB8a_oIXzt9q)C2f5lxt0a+A^<0%15n0mR+G}@z>um_f_x$gc zriC$&Jsw}YbVCZ^BQ#RTb0Nr0z2eb_w^4ydZ8<`6R)#cGIy4- zAkLFPGc|IFUP@%i+)A1i2?Y5n90@dm5)Bdx4B8cviedCABo$N0+34tpW;M?e@fAws z8T$W#C>9TRs!R4_G8BBK)rImbKhThwo0@2%f-o0m(F@j8&+AC@OcE#~-7`;!S9{S_ zK(W&_zBDovg=TahN=0Qm`WSnVuwszG6o~H7k6VRtyU~`y?sN>PBKK7)J%rQPPz zPNhe=R=L48sNA6(0*93EaVNPK8|?|+mvmg_jro=YqRHT8kyvzzj4rb?O#w1$auaN7 zl4-jP?5ab-qQ@H;2#yAi25C9CKS%}NOZT!keNScbxP5wgf$hq4?W*OqtEOj0%AGTv z_y^bfN6;f9!W9VP0^uU@)+Ka?PihH1tpJ@tn-r%hu+h>)s20}N8nk177{5#PfEOu%!bQ5R>VS|XUhv>*RSoOZPrF9DADH^^ z$2)rv?bhi|=xKJVL8;k3doIIvZpm&~y7}1P<{OtSy6}^qz~!sH`)$I$7hZpOdBhVR zehI#|WT@D4)6ahXpBVWo5eu)Oj{!f3(&LvPKg9=8q;=qhK)^+s+FWhGFagAqE))O) z{)*t-Bw=9OF+Cm+>H)i?c{sd!fWqq##_`o0;cRps$5iHCC0C%%&!5I#!?f7#gl@3< zM@YlyE0@az{zwR9PfcEw0nxxHIE+Y+o}c$2H&OkI)m0A-80rG2aK|_ra05v69_ASC ze&##O7(?Sf#i2*T9-F|h_xt4-dQRL$PmA0GKP_rdeuxgaTypH=;z2)k8taGh3*|D( z`tZG?JH>8chQ0k_IXnOc{IAlMuo6KMGg1tik)li%UUhZnc^L;DnLsg>Cx_QGX7Umn zwr=oU8|)8l5;xazkhZd%m3LSISF(GlFSC0b_X@kCk5EsF6TVld-@1P%oTvUT?eh+B z1N;zry1mxtxu3buaVUOr&U+}@iv5X2{?y%OT}Cap_Q-uypY?ibgxc%d8$RHB)cUBc z!cSO_*?vm>7d7QLZ}an~IS9DZ95swvJa+rhpdI5_?oP%Jj9>uYs?S^W_6K$a4h7Bx zm_R`K4PHYwcN&Ey2Fq(7ZeBC5@D}kHUn4WS*lkms*DW2uO=L#@jFUA$!WO@x3UQI~@m*7%d^f?!F;XARSv2=CC zx@wvUr7x>yhOuB8E~91| zhH)1E-{`{|Nn%45`HzrF0f+vYJzjK}%|$1A;ja>mrEr-5H<(Mtr0SkjeXq7>tM1vV zdseb%x~#>35H5xlPtl29gfc#sVQj=)!Ets^093d)Ck6uny9a5r_r?c3oSmug^4OsL zVqBb0DST|TI321*NH30%nwqGVvwsJ-Zr!tKce5P$`Gb$W_m>k7y*RT6K4B5UTiVv# zO)Y$R`}Qrj`}e*9;cwrA+%LY@v7w>eybD*gdH|sBuzVTl)Mb~jg4RspVY7+DS~IC7 zNIDc;EaY8j$m4zJb>Z1;FLrE~mrzD^sRxV0pB2SC8{eSCLIAka?h2H~yewXmJUuB) zmQGCz)779ii4%YqgctGe3uK)@jQ~ypH@OC&OU-DSZ9t#Iry+4*$YRfhBoiRF@tSET zUQCXo{oj#{z~yRgnh$ZW;|==rr%qv|jooimW~;y=e(P`S*Qp5a8jtb!*TeA`SD|@Rj>itgo{Ld2PlFbMdM`@& zjmEUs!}6R>K#oy?S3k86CHWQVgE82sSD;`zQ)%dRdxZPlZrFf9{?x!gp0phu)wWcs zwhb+$HG>f~zuyJ1YxKK@T$8TTF4h%pdWL3ct~S+E1@o$D1bLFpccSL4^4SqmW$Wym z8R;y|plm){TRGs>lKx;q3na8)Dgu)Jh6tQrD33xJKraN) zPp2*qkBx1Z82z3t2#*DmAubg>&w7#jKA+_XsYSR^+d!M~^NppLwpnlfp zYq{~&%dbEoVbSa|`u8ZaSO~5J8|fJ`lkx>`u=ksZ18p>D7B*8m8h5OrKqH%Buf9)V zN(*{6Z(DF{VrcU|W}oHm&^_Y5!o7>{Ub=6^7kj=D`i6L*r^1}HjEBa>m+~*InB07N z^VH^Zo25D>kP{02HhHt<-}vsfk_3Wuo7ydbaM#8EUf=5T`K|n@2E7`trFl`@)36!+ zjuLK7yVqViq#f0s(`c;%4^C{*M^RddPBoqQT<;!Li!M>oxu&?k8+M!M zXt#+DbvI#`>n00QAaDA%@o*Q9IvzYlP+$*q!LAD3VmiXzwrm*ohDXCx_=nW50gIe+ z73f57*;p>T8m?|?a<6)pHc)(xp=}YYq773P47knMXB;wUL&Wjia9~$53dIaP+6Vja zvpJEMqt4IA1wZNhk<_jFaFNC4L=M=e$tjp3*bUX?_oZM@YA7|CI-O!tE_`NczOIZq ze=@z8Xi~Q-n~lxp=EG>rEt~PB5xXP5*|qP1WpEj(cr9yDg3uit3jPuWm&)8VoHC;VjaWD0LU6Ye8t{EgX_c!Zcn9 zg;dRmfX_!Kqo_3gOZALOp|~`1ei|?6$49g?8agsEUj?rf!AUWEzP@(HiuD~!8w!zV zNQ9Pz*4mP5$+yt#Wr?0dMr%y0)A}MXvM?3_D+;R=a5*d~V3DO10lm$uBH(JhPk~Fs zWf8bGxjq8dt&es{=yYizSlQC8z!lwvHk0a7@N6X08G%=2S4Y5_^lAkx4Ru9GQ9+-d zzpZUP{(l@b>UWVJI8w&Jb(n-w)0PnsMhk)$*X7O~_ts`{kMsTt_kpPmBB&z3_VciZ zhb10nc_{PH%|kEEvu0a&6Oh7P74FQ$U^v{(c9I^;t!{GTWf0YPNQS3QIMsh`x%NZ2 zXMP5ktY5-44K^OHrI4ho79zj3YDbck=Fl&>3RWkGNy#Ibfb6AvXur_6>D1x72Y#fx zXx2iz_1oHCcx=h?CRsHiL;t?0yzMjJy72t&6?RX7>(A>&80g-*B;UL8`lY$q53@$c z)*p^Pnae-)2D~c$^^IS8!L+c}P@T=fUNJO!!k;MmJqpLr7OQjU>ftT-uWxM=MeTCy z7P&=^UrX)TamT^+myg_WXw&~=>}#Ogwyres0FnSfkO24#fPbJ!5QO+AiKHaTY6K3Y+ov06{o&9;+B-gh5JS?3`U*;9#Uf8 zDghV@N*?C&G&JNh^@dqS8J8f?+cTt(Oj&_G$`BZWR$O_PxB$!Tk&G)BY>_|)(q9>2 z8+4e*Kp==W18)VqnN%Ia0OzKyhi|g&t|4 z=(L>0=JCQR%Xn8k2m`-C_V^RcI_?y@lue$11D*f^o`43v<8e6H*&PxO835J_ADXXFyW)ljxmmllC>*lcAbwxB~;OSn0Y5zi;w zY67vG#wGzgv^AQR-^P?`v8=ygbkEwY>g z9|*6>VQLWj7`L|g*DbRdZQb{o;p->kp#a}yFJBX}dw3?jwyXQj&5`E%<$(`m!-|;J zBGmv2x*uJ-?NjS^eAxcZ^55>%l|(LQ=B4Y=+ArTyt#4aS+)_#Bav{Ed2lD~Pga&(g z5bsz#x_ndzz3dB;CtHK__4eR=2@moRR8Bk`(1EAJ z6T-;$6+F?L9hZJK-ts0nk+idT0AW2|TmXq|j~l;EMofn6p`VjmwzMk4!%oFk`}#cF zZEzwU^U(026s2c6*bv;_yLJNJ>GkHaelmLeGz=0y89i`k&gfAZZbEWJC?hcbL+X$9Bsoc2a|YDPo0$g15R1)a>}eh zO1q+Jbv&Fxn>+kgDu@S>sSfL@%vwPeLeuzGniSO`u%RG7?M2=u;DY1VHK4G?p$2o8 zAox{(PO*zkg~a`O2kO%b8dcDQGO0`}$CQhTTREOR{#Ru1kThf%F&GXkI*=Qq0mxlg zN$7Q^O*}9hr~Dbp!*!pw@kOw5C`^V#s0F5nv_%*ZtFlZ(E7ITFyOQsVrQhmYv!=6i zaLt2i_we#{*Hz+PHl-yB0TgyW3I_%|JJ&2{mh>G>EbiLic695PO1diMCMo*z?aS-Y z5%&=++ZMXGYVPnALd5I~rQuh;bP|+4skDUn^|lcI)DAgP?2G^&n*Hgy%gf{~xN?EK z1y}wjc}p7J;)S=QDUW4>R0{BH62`v6(afYNXf6N~2bcDg)&|oUXSWLrG;K(*{rox@#eJ-7z){&^A z3H_LUQD=0W_*>^L{}$WG;B7tLS^S3#NCwHDYejh+D$Tl&tbJqaB`hNtCW517F#Ld4%<7C3W!M~A*K^36^H{qLv4!6AK?##YgI@xTKfYTD-;I@i&&JVUOe`pUELWiF5j9_ zD$y&Fc7z=6EDkQ~?|<~|^VepxJ$`nlvGXtJ&wZtsA<~B6F>@{fYtlaE_p4;Bs1f*~ zlD$JvlF1%9WF^55(h`L|#wKqPd4h^vbU;e4SxKW7JRw;N84m$06@ou1Q4qvR8>dv- zIHdx*89JyyQiDXABqXO@c|N8Y1@b{)m^_2gN}g(9ISuv_w9?y0F)$)AW@0(}5S57sR*dx5P#H zH$iSl9#V$V{rXUIy?B7XN93w>r=%NtzS`_Z{Sq6Iwxb)Qn{-!J+KG0GZ%DtD+?(W$ z>F2%A^Z$!?$GlM~olK|K(btL|pDc#_nvfJzfwbpFX1nV~_n35(d{fAyipgXueIxB^ zO(fM`g~&gW7~X8)@8|&m3HRf1#Cudz5b#I0&=qmDW-1UvhSy*kkeLBj{)Nm8l}fY! z3a%os3h>lfJf*D!aPb6YZ=Y?k(VTe@1v;L9J*kSD8i3?KHh_+UsV;}*Ck8| z^hE)k6J8hSNnsx1Z?V{nf)p*?L`@8>s9d#5Riw&nWxjH*;vTJ_smgSPu1xed=g=ol zWWM#sL=Ege0DL-(Zm*XP0N)(^lH>aauVjcX!l+l_mcjJHf+Gz=zPLkyBFuBBz|sdw zYff23gE|by7FZStPzMg|hw$71qUO{Zqgu~hN zK_NFqr|>!jP+p#t31z+57TGa`)r{>!lFpd;dlPuCLs{oNh8AVa0->y{Cci>i?`c!k zkqp)rP;DETh{waSA!3cy+8OAmy{JV`-xcHsDrnnVn*}bDM~~j{@!_}MzOADxr(Uys zT|Qn|{-s*kx?EYGjrhcXu0=X!B)K1bfB%oy1_dFUq;;LHta)|$AMejp1AH!rBB59f zeQf#MSigdDIoTJ>e2}?zW?m zXaPKa+`{qbDhTBm9z%myK+`zdPyCVuGo6f3 zZk8ihiRD*TLqS)Aqqfl8uhus#4-HkcKw8lXGLqeozBjz1KS_WPV;|i>Kt7q7JYCEa z)J_+-YG#U!5es^Zc)epX`O)R=BVFw|qzis(_+h&UEfRkHt|B?B*xk3jeb)N+S?ft= z7@W0!c*8I`Z<=BBRq67QY3FLlD8N82$K&Q z`XqQ;A29F;gS#&Q9rqE#9!~9}$#Ig1)+dubWYUKWv?-?#y{i?`e%|Rr?p$G~oB9{$>s2hRLg8@@jYl-^Z^`J3;bYec&2_=03+qLMrc&qkVn5MgTWJ<4AjK z;mYsG(UFm{U9BM{uk0m!v6f+4ePtRlo&sT(XS-uGF>{F)Ms^CpF<5>?6WGxTz^*|n(UkHqx+yOx^?GpZe%3A zb#yB|wRL(cO>LF7(p&L}KNXJFw@&ODo1<^W^WY(64(<6Yfy67kr%T{#IPVmL*8?_& z1T6eFME)}T!~qIc{DClr&F^5>^;FfU zdjm-6>Al^`2HtQF&T;&Sy*@0I3JGA;$b3Z@QGJd#;$QJew5BE!!(iFi72UJa%&u}) zTm_|TXmn4o{71DN4@5rp=$1|UGf_X^`+?=b(3(t)cg4*ejk`u@Ix?_+xqGDPb7xB1 zdK)*E)$T3JYleC>0=P{Pg^TosJz~Cd&+bod*|L4$f#px^(4*;GE+%E=QS_&i6}z#~ zS6tpg92$6%y8*X#+sRVja^&XTcrF)TvmM>?m68L@vHlY%iuu1-|J4}RYW>$B`mc*1 z^KP;aaAGtIilhRsvdNsq5k=O@7baSSixNj;D{n;jLjt)&FOGu8KM$uzsU&%Gl5~_L zT_i2yXS4`}x7twMa=;>~yxYo?z#5T8W#sU=tXINIF{_k}=Vw614usa)E z1GGk0W3(~Rm~2cp++`QCNqq{xnr(Py8|NDIYy(Z;hWQ4QU!18@N(<*U5irdh=2lszwIXNCp z;<34554!1_8#OXAX*@BUXU^b}DN;$gylQMFFc5W!wggZ*_NWBZenE4=XoG`Lu|v@4 zBB!}x2IrFu%9(*3s-)vxxk9(`!f`lHgEz-P3N=@l$Y@xQ2!@mhfOHYO0acePB?t~} zhD>=Nj(E zgf26aMlVEupwV_3ZQ|Nj6C{fn6|wEI4GXUeorC?TYr-|@ns&{)Jgy4@ghXv&2AgLq zOc7wYgHyY>WwvnRmf6t{-u$GH+Vo`FwdsbNcRdTdet2jhJlqhb+Us`xEu}F%l#2>8 zJ#R^G#aF$=v(Q-Uh9DIZC4)vFPaBCmZ+P;u7}lvI(sUH{;+n$ZTF5WyD9+$r#23>k z)s1&8d%yihcbh^gW(W@t+EOD)`ChJ<2@^ZhUOq$%gbMB)J*KTLeOB zJ|~n0v3W`&G)ju)Vr1bWu0Q1s>BFK=d^ z#0=pFO0#82p@h+d}G*59Vehs$7dBh0h2;%0AGv`t_CiE@W~}d z*P<1^*tr&AtO!#7AioBrxhZXuJ|sONyB;c`HKn07TS^})-6`K$x|_RCzOVEr+&9^` zxZivIU2AsL#_D_PE_)5Ca!kPrhA_NSALl zRSEQTr}=4~p5UkWCwPW`Tc?RDGp_5SK%bty zj;;8snEWl{xm8Pwfr(VyZ?AI5p5p@^nb(j8TiUNg_^c)@*~QT69&&$L*+KNU~~yEgEV0 z4M(>9>4*0}Jo%l?y@j4wbIY=>_L-rGluaoHs(S+;zj4nsAN;VrtE-x0ng?FF@7BG4 z^5ey$hazHm`K?=ODZ@ZfU-us7wy`cHaA^5E`?3SOuK(zn|GxivB?x@s<;^Y%%YKq7 z((YBVujPsA%}2;G9uW^>Q!Vd60BkA35e{e$DT=Iu1I}}Tq_EjQoB;v%(~dL1me{06 zOa-%s;<3hpJ~rUU85Xgj)$V|KBH@mZ&Br@ICC58KZsVOGvzn--c1R2=6H`G~%+WG! zm#51ArEsk5>e9M0L!JG_ZIZ3onQfgLi@U^8Z7emKxw&(9ai4UXc3Wm&=L6Dy?NDle z=1}po+MgG{CVpA_TI$Q0zwG>a@$u-lw7*V#w|FM{J$wbP6)zOuD|YJTyN$aGM?zl- zeI-0!W^W9k4lZD&ShM95h$~_$&15wT!EI%Yq{6bEKs-*R(*Y3EDwRglh@L=G=n2Fi zj(9NM&UZ_Z(FpxqfUqj?&ek`Yd#p$M4;dm zxC6z5uaqcanON#@jsHRvaT+`yAm30SOulrw(^};nmKF$;Z+r#y#_fIBOci_R9?_<4 z@iQ*g?nJvgQ5v);v5Ozdnx;OSPnl~e-q$IIbqTqYDTw$;0usg;hGMbhyW4|o5Asyf zov4$NbLn(iM^pN=PE)#sE#Z0nobJ{qtZ%L4(>b>6f%E&Fz@M~uU~yb-WwcVO0)PiF zBE@R)cvMk|m??ZgnBzhx?qUj#3F@qXgd)A|yoa_m8C!{cU*#7ax%U4?v7CHa+{}v(k^9 z*?dKUdNQKPxjLh%V8u;$cC$`5YH*7i-Q~XDO}mXwc5MpnN&SZuoic(x1XuWut${>| zVy#Ex0>m~mgHV`et*13>FF54_u)W7&lNJ`nhol9P=h(^|v%Dp>L7I zaHuq1`s4D!(h+ooKEg~zrnIT}RN|5Hk%}MIPT?7^;7RbM7dw8C-nv26I`z&n23qaHr6Jqbk+TZi<30ReMlX z6-Z_ZN2~C~(mx6!6a+%sZ-B4Jgl*rXh$h5Iaav@=In-;brlwZX9O6ndCYm7W;0)x3 z)8^Air+Se7oe!~h5deCdm)oCXC;&I16QqOf~&1;@rv&C*Qw%|v#MmlO6!vBVbgLZfjkzMpkSe3 ziY>Sks6APLMG31~yvM#Py(_<0aE*_l17x|9UA8)dX6PAa#`jhKbYxnaj!!2Z>-b7` zrYv9xUj#-RF#T*_HCxU7S?Or*XvsYe&vvxsg042b1+|H6zDeUP?$F3J!o1*{72H%J zo3|-Qso+pRhaHBo$Ky>>sLh;1#?Ly1fQwV1W=RP-eFdGq#325NApVG8v!n;%Jr`|J z#HWc(M)Kp&@k0j}?Vuljjvt?cH$^7fA11|K{fC?^*|E6or|F8OMJwH`%6Gcw*f&n@ z?PP-~Tv;`Dwijk`mSky7k+Enj)=u(*g*D|`JMA!Mu3nCXp3da&{qXu7dV2TgfB5V@ zH|@`9Jz^E<0g(JH<@4EA;*>GAy-Of z)=)d{8VV&7s~k%t=VfT=-1zvsbXHmzZ*y0$FvQPLetZGLLUT9TP18fk$K=P<=OWKV z=hQb&^ZB>wu5 zwo$X>q_U4+8V6GDCCF%BI`70+@uBlbhV|nCCpzmyg0*r0jFnSygmoKo5!{D7B1iJN z#g^<6uBo#UWF#vST-R44s4ou;_g=D`_U2b!sTDHU$YyqGZDm*IU-aEwj#;knFaMAA zOMf$Vja9hq_S){-=|9Ru@7S3C2s~2y@_A;7`66Y|yH?3x)Fhr)u4PL2^g=88(!ykV zs%1r=clbBDMo!d%B+?UX3udrwZ{x}(LL>|3+U9jYF+4s!pm>s{fRBZIm8ZbG=6I_5 zvm)e1IA+I%HVuaqke9d$%k5;HL>`9YeY#Hxy1@9!{rG?!*A=6-SC14O^Qj(?B zKqgp1x(V~otm&%z`qBgX#`H$rt#P4kU|nanr3^F6nP`|zacgy-k>uvkIy=NuhJmL% zxSs&;<9)siNj3~nvj~Z363w8OkqZ%uWl+^}!Qg0UIz&(5-E4>f@zh%aip4Yk{GqF{ zBA)6X2a!DbL250eL)Of0uAoJr`@|EXoDj7HCCPCqnP@NW2=q!2+2c8~PR{AQ*l6T^ zf#e(~9LE;6L-naSSaMZbVOv}=d}JG$mJrJjjc0BbGtsmeSpIeS6A!H0y1$g@+lYq8 zhKe8G(!81Z;?hrMNHXE`Q`e4tZVEj%+!IH}($T5W-VvI;zK=E_ii%}<5zDep|LTe? zpYnPsE$AUnE0FP~w9n7G!%A&*RK zTRG^VNek8(I;rT;&)MRYe}_0<{n=TGL%?D?rYp*-v6Y1H9phvU#Z8rZ+gF> zenWda{ypv~?_dnP{L@e}1;{$1rgtvusJ9fX}YQ72`w+bL6)Q*PMcl&0yF z*{oB_GAV6aP1MDX2dD$+06pn`K>xJ+v+|J+?*Lcl>q;~Jf@ePSQ;mI?e?&Q~GJU~~ z3LR3ysSp*{Q&fkX(FCW*moR`Bjj`g@*E)GM(P!Pj6%A=`1 zWe(v9Z#R!5KF81Sr}!W9ZvLP*4#FDqQe7sPKd#}x0O;s zxFS6bC{7Cv$~%uuyE*jSDX9ZZbvWM!J`2iIVyK=0Rjo=zY{Xw8Pc_h%ls8~$Amx&_ z2;~7~ab;m3X%Y1G;nspc?iLB=Uwdc*S@~yte;b*QAyPz;jPMk%NBu)s$-Q|Rm)@KY z2%WEEw8R7boewoR9m6HOwOB65aj)8buSNwunopO*4CO3Ti{;JXECZ5(Y`pgRKA{kC zN0AkOJlJAGS+W3-DWfM6rWN_gD>2U3QAfpkIGb30##%lTEu`fh=8HyN&vq?)Xn%h) z;1zv_;gVD9-~Tn^?yX8*4x)4Z%jexE@wh5EnN6*6mCvN)09^vtXn^wO70y*K(jL(R z17c{XT8%ZOrE~aie%0tYL*?3XJ8vG(*PRW%*YN);EMPv%ScW`29@+p%ve-_CPBqc7KI zZlgxfR(2D&A-CS#TG(nm%uY#D@}CQh36_S8ZQ=JHC`>8 ztE4Hni}gfYF|Waz-h#(!#IBRBlSkb*usf6+tVeuDq=%J9)JL)p8xQBFDzU@fhhm5G zjNdzk?v?J9U3m21pVTmT#NzQI#pINvXEQ0CvPvmR3SeA>=T%8cr*)m8p%GCfd6 z{0NV&z$Z`6Qy;OikS!Lm>dWdsVg2~|Too&V_-S1m_P4YEZqjfAKCGPAFpPt({WulP;mQHL zYcE{n)E6Dg5bFs?UjTcKYby?JbxZNze(uWZJu7j8+@Q0l7#erNiAAhfK&#|onO88S zAW<&_8YKywho*)>wWm**}OCkSmo)mzMq+efRKSEWj8B6O*#x$kIFLy#7X7hom97 zKR;Ax*1HBa`L;>hLl+`}I79g+^ohoXmKUt(vZ8Ts4FGx=1& zElQ#+meNvMPM0j+id6@AX<)ne=HAhPHk&nvulv4!)DJiEi7KjA@^yuGQ5Cp{R3({g zRw@I{wgRkHo%MK$2Ap^Ha8IAkl}fW#e#f%R+9>|U4>nN4?Pd*?WpfQ9+-Mj=)HJ(l zLbz5F@UO3Ug;>orR9}BJukegeXB!DnswJ=Y+U;TjCDQmesaD_z!5~4FVdk%tbvWuO zqjEWwO7a2*y-)3nqG-jKLxB^zid5)AkQ%l+tG=#YR9(;j^S=5F-AmOd7Jck^qhexx za)PR%+B5X`;h~50))SetBzzLl*&D{Peg8O9H%(iyl~=%#RQFNeg&n@;Yq) zkFhTSbK|P>e6LDp=_r+?l1eJ6bX1jYsdTHQ?v~VdyWO_iP22G$+p*&##)%J14ksqe zB#sl3;BW;dsxln+~>GHn7!L)>)0C4`XoQ@x17txvKFdgFb9Ek3Z7EF5I0XCCO=ZyIb?~TNy4&NRrm+ zzw_#qgB3h3sxwah2FbZRv0;UrLsoP$L4v!YFzQF1jT#rn**oVLbrVk$d5%yw3A~wC zH}e-3ou1@E@|Wn{P%gC=1z=C^2*xDvI~n459_EqV33TFdd=1*^EC5g}ESX$CY0V=) zSCur~#~xp|t)AQTD&VjFKj69gYv4s{hO+gB4?b)p-oPu*p>o4VU`r}r3tSL$KvVb2%9 z7mT0xK9PI{yk7jNPDB<_GMP#wD5!cA9_)ss@;x^sVhDP?-fV($q93G^N!0;KCeSGf zuTeu8QfTc#c8)>pxgzTHp;mOaQAMo!>7v{1^kGR@e=A-ce?0;?X~38o(o1taP~`Y4ju7N5?vO_#g3@-Dt8P_ks&q8&_^W`Os)8QE<`q&1Z-g0~yh5 zGlOoW2J#?6JPUuM&Z3~$4H`~!Msvn;#`=`zf<}MF4K41ET5{zXu!){wNnYc&I_T}1 zS^90w>$JXyuU6`~HTKtt4z(%vO`#%f#w|L~Z25oBPQ<%3vu{3edFP zIFQc!B1h)#xVee$*#m%PQ2R;Hg#V&S^F@~!@-M`7E*y=v09}7E4X7X$pxrc1W5h18 z`dm|uVA5JO-&Kq2V@YaUU7a*as~sK!ZuJ*#b*!gb4ZXYLayMa3hi3r!CpD6ZHh!T# z$(H5ep=;)(Ssux$J~LJZR!mW|;jnVJNK7fs@+Xu}l%GTg@7YtBFDyL{|w4^zseGc)i_wexdhKb>(TZ$Z#Ps@b- z>8NLD_yXMeV(95^nj_*Cwb*m7n1;$W>KJZ38x7{4g@q$bcf1s49$a9h8{~8iG!m(L zW3WN!l97Q_GYA@q{-B!kkx)lz;D_e?`C<{IDJtEMfc?_JG;qbDxb!7;5X5wTlYtC| z)BsIzF{!+ujx1VCb`0e!cz(4-lcGB!``SWI-HB?{d1_p}XYbMOL^a6zfQR6HheN4S ztJAm3=cEkj<8PadzEm*LF-zn1=bzv6+KG*ij-&(OY&9hN5`{euFZo$ryLPC7Op2ti z5B{{}pqchF2ZJ2rlcY5d5Ob@~U%I2ZIU3H0>lK!sElr-qBR<^-r45AA2q?lIVw_%p z`IB9&zT{4D9I)btN{kL-z*>vCbb~eu>@o3rtIoApp7?G#99^+Yq6jXdLP90()3n97I!*^ZPBF6l!+(H@NgN1 z4VJTl;lXeuE(D`t;;f*5Q1r%yKvZNbsid1EIBz7DaHkU&V6<6CU*=clJ4Ag)dzC0|)8=d6|b1SlAwbsZzH~i zOopeNLSN!rTd=-YXEz|911{LVEEAhKr7ryO(1|ta!4s zGSlqeuFo{%+}EV~1Er8;3XPv!vUT&#UVNN`YJR;Z0AoO$zn?E2=APqz&5=QFh9jD2 zv6Fk8({Q~Uf3BC~&-HTr)w5)ex44{vHYKuA%aBuGM=g36(5pFU7AUF7XC9otuO^>8 zZvM5Iz!_+S`2}|`_Y4mcUKr?A;&DqB(`#ArMKkL%}=jmrldDHiJug}RRpL@Sd zd5puJ|{Aj$FI28M_ZSDBZ1+ zv9c`n#&olpPB$CcDqi6aqWIO9p$xwYe+ZCzeH9Soc>u{5kZdKS#^|#~JO-%jZDwLFfE*`8uHUG8|t%$M8p-V*tN<*|6dq+aGa` z4gBh5TlX9r*bBC3wrbV_J)n`i4S*<6Km|0w1Xu@d0z1Gya0uK9?gQU#?%scJW^QiV zrn~O#8#sI{mfCrXG(BlHj5amEfZo2KG?0>{)Bw53SIjzXHqN(p>W-sFckLY?{@C$y z{`LcQ*X%|@SF3MC@9_35f_KaD16#HnI8N>jQ`ST#6AAAH%3GJJ%B9!U>}Fh(`*r*X z6W1cQUBa6yKkDzG(p}pwcYlA~k8k)r>H()1E)@&;Xm9Q4t=+x#;`jRN_OD-G|GnOI z?Y?~7|6jZw`9`)_%zhRxepkp9auVL^RPyNcwL&geAZGEh?ZpR(`(AmX@(HxaqP@SMy4#J&CqHm(3Q5 zVDCpAJ=*v=KJ9-Nv&9VBKuE%~VSEh9!T`8Uk9Vfh+mqCsy8A0a`m`wYRSjeZvUIX{Af1J`Pp`AQB-y{e`!vg!!Uc;l zo?4%J9M88<>t%JVc;)c^?MT)iAb$jSkbB8XJIzpc0j_FNL1)%UIK7bPFOUmo%`|Vp zUfDeQgG#IY)@!!6UPAz4!&ahOR0WHV^O_!5GMI3~(hx63bx{dPaDA>Jj9c7o%B^vFEd8NP<6l4iB=G*i0^No4Dg3A0) zUbD65FJc;^13$*o=)9(eT?$IeUTtBs2Ig`3C@Ud`GlVMWQNOV2h>#V16S`&(Ta|T9 z0iVd$No_cz>7ErT73)Mrr_g^O7eL4oN9rsU9%GKPLqH3@#3Xp zDWN zw{Hh`?%uoC(52GCB}65aOkMJ*bShj=C?xq>EnE9yK|$1-$H&y*Fecfh6$I0%^vaO9 z=W%%jHl&twMPza>0lMcBAj)+ef)Ai1fhp-Omsb$7Sgu$J&9}$)kBjMm-Q$x3v_BN} zcuQ%CvRdGM)d?nP3e=FuOIAackaN8LwlUZ!QysG?6$13Nh;m-F}ozq6-!b<4@R@2aBg&N^p<1W_ujH?gDCC0W&76E_5QV(w1I7d)0AJn{q{`U z;c`Jurc^4Jy!Mb;wsUb& zl#_gyG!*Ud@oP735OwiV)%{6cb%59zC!az5y9MlP<{n%J_pQ5g9r1A%vL^rONjNz)1SP+} zxGgEpNyT)zQWT5X3YgPP%cBDzk;dWdB;~$r)6t-DX#r~qw8SiVjp$s+V_rnkGnZ|( zkZs%*mZe3`aTS*IN9a0kTjsAtj_pcVy~?nNgyb`&9S_W8M$;~a0-R~i7{Ii|oVq)cVT?xZ0p{cT;r{K1PY=Sua(PZVoIMPg!_48( zqn!PIDa4CXD8zv5ho+?mbO$c)-b|m$+;K-WcS4TGN8}vfDZN^q+G|95dA>8R@`|!x zQ>7#B|M~gC`^ZGw5*6?Wc9l^I_-o84f3%1ze30x!q$vC$!ourhBl+wfCGSprkOZxB zeYG;1EbTanh@V;}z7lqeJ>nN`6qV%EA1VJnK%yer{y~VqjW^0o0#E>GIPzbBjO6CU z<}u_HM7tXA*TYV{CiwM{J{X`xmdi$rB_jOPs;Ls_-ZFA1`Z!VcQM?DQkMnglnuwN;St*@>%10$2OUNTSvuaWX?x_z(eBp_{yvv2AQ@SK z1t^ev_) za}_UB{qSZ!M@t@MI6*{nqbaB8R|WvU~D~<^Vu}^{ZMgs(bK^Q`A z!O&7cg(2Go)T8XW6ifvJ5WU?dIoD`xtioyz!RbY}9K|&o8U=(N++V2Um;&+;x)l*F z6;y*7xte&`T?>jCa#0aG1r2_Hg`nfMYM14O%Z*^9$`SInlAd-2Sbk_?N?LtlOJ1x_ z&8!R8?yYg6&utYAK4qkJe9bN2cx=P$Bd^@LW>1(C?37l+FtkP_o^T{mDHophj< zdh3qI_6$epSb>ekQXZ$RHa1?9rcSNt!LBC% zNE+7Cu$p=}Mbx73K@*%Xz;P0e5m1Z!C16Vg6OSjJOlT4bSJ}@LC6^$|j6hRGMfM3` zCCH$%@RpiO)LOs_u=`qc8!(PmI#5vU1x*)3=xf69#E%~zw0Qh&;CHkkWA@NFeJPlRR2r zx;v^KQZb-k*(YS4lC;^8HR137U9IWYV9Ul38m^uv*>rq1KQ}SpmHOMpENZXbTS$^` z{(N**Sl2nur~3SzC!}iJ)j2ODd9x)sGkR4DC{-`TCtXJ%D`xjSmdrg6T8CXrgaVy+UVy1@x_$7&Na**SO zOc>R6%Mf@V1*mW3SRcC1PI!Leq)&gGK)Mkg%+zdtpYkETjezaXW6ObY=)5!MN( zg)@S#(cAc*pcMqUlsD=GeGy^dyySL91(%Giv2N@D+hn+7jx1n2ytT*z^X=XmA)?f! z`HfMO5PKss*slLFq7w$o@PC);LCPE+%y)u(DaCjE`e38l@l+ZCyIyI%wZ`APB5`3*x$70{)mS~j;%ob^zC-Lp*H%EHX=Vdapp(*3kQNkl?U%gX@MRlTH+e<9 z`sMaz+gmt}*}a}qr^9me8ryS)@mUHHJBwAXMq@HLs>7>9cQr4XoPGzx*=YmA8(EGb zjG;ye{*XK?osKqI9?*}b%A^(pl*A*0?>We4gl&2e#P$rK+~qf0U`2z)qPEn%arQt_YNJ0BaJ<4Yp#wr zB52J{Yv&bhV5Hs&W5TK=ek)mzJ0N2U`dO#ft0mtQ39Hbzv3{)cSEFm9CQ_38R)f{% zV&K-!>*7F)lO&(gA0S4Afvg~krqE!a^D79bpeWN;yO9)ocqG4$dN>S9O~%34?OrL! zdc#`D8w9u#@4`8|NoEBH>ZgVFMHLzCMe%#7umq1KtwLVYR>C+q8q^WUh5iB8n4FfS zo3315YR~^6&$7u;rTrOq$Z6EaHm>cwCkaZwpBM=ZD!h7kVMPD`KzEl1|FyY)3lATJ z@J^&TygVsp7)Ub#hF~~aW0X7u$0duoNHQcMkJ}~^qVKN&JPQ645W4{!1#&lC~s~jM!;WV7!o*7|QSS_9DS3^8j1H2IwC~x8_@u_Amd(!ljQ-wUh5m|>kQ;d9*w5+La6hC z#uja6JEtoByq5G1RLM8nDdG*$mmiC@|2aC6_eoNyvMNs8bmbM&*M7R#6wL?$nF>!( z1DmQou~#=+^BC&CDEKeUn~ik#^8nrh9s$H=0AmaUjGd7na6+Je9Ks<8Z7>M4kc7}4 zkMg0c%>zBO9UAN$FL;=f-M+VZC`vCFLkH}~?Zj3)oV3HL9oi!TFsdE*#)8^Kd1UK1 z-awn!=!eSo*2Px)vKp#DUtT`db9q`Vu8rMecnxoe*Rl=LbR{SfU$lf9Id~{oj<908 zVhRtHIuBI`e40=;9`~ZosltV>tTp&6nJYgc_3iKD_zhCOxrr39PfWPO(dGx@VH3!CIhSWM^qkAWZZyrl|jIh1`+(T>EJ^IbV z2VZ?;hRvT%;Xc3t$nw4J!JAD2niDM<5qtI=utC7 zn-WwAMe$czT9*7G=a&;QZ8jHKPH<6`qAad3vfJ}rN3Q3_U0gJ_Vb5t(tC%9HRoEL% z?InK=e#1StYww4OgWHdm8oRPaacrR74%Zc)3Xj&{MnfQybS767{L#Uwusq=;-y~_V zv1?*--;h9?I=`0(;w&C5>TlGDsmxH^A$F1)%AgDN&*mB%@~mEu=^O1_(F75FLtqfR z+nk=L!O0R_m4XM7$CJdK=trZ(codeRa4Y~T0eF)DC;YJN+UO$6cDRw*%iO_`B?eY4 zn=Q9m?zU)ZX1G}gA0m(`=fuRXWdFnmKjMSCd~n7GhkP*WgF2tf7x9s&No4ljMG`*J zM~1(geLhRrvcc@*Su&e#gz>q~Z&L6%3jPHJ4^j6~rzw)E$VlV@a6QL1-Bpmc4>cRp#lOU@_AIuymEV?U$-g z$Y9ajT1Kp*PV$Vqiopi9z1j`DsCgzKsMGZTe4V!L8afE!nyzYxE>{_Q){BrPeVxx) z9gM>)By)~VywC`ls8FK_p9!v6KP$&de5zbltPr{r$*jLUY)@oMej(|z`pat+U)ct~ zi#?&vZ>x2`R+5~l(JT>F#!{@Pp#!{sTYqM}C~;Oz=R3Zn;M7U*_sBW6TIpn=;z>}^gTFh^m|d$8R9$#(2)b%328%?oSYI2Hqfs+HHdp@|coh&I1D^!M z4sZw%831Vzz~Kl+{{RDLnO)3LhAc9pNVO^>e-eW;F&K(L5VOUISgfi%QiZK5?60n_ z5>;L=k4#Tbj$jBYG9QTqdF;dB?p^d^TIwiM6~Byqci;wAL;e418hqhIfEn^9B`i|Mi zP@m$odl-{O6zP1ezaVR+v0^Y#oh{~XUYixi_Kn8s*$Ct2OcCPQP7ecVhkxmtOix18haBx-QQg= zxv@P%sq0%ruJ>`et}T8_)HRjwtu+HWyF0Y)mff)*e2>EZ#Lvo&pn;%+{aZ)E(-RHW z?lK`sTFWBqn9q&GI>-C(j#npQ)^D7LPj$@$HZ_*(++?W?k5y$0p9=EjO2rSq5-dku zs?9{9_G?s&_<`nlg@-!c#YcG3ZKxO!ljsZviT68s1I7D8A-|vZGK_)Zyaq3o<-AS~ zeeiW4Vr001`U6Mqko-|y3q0su4F;k9k(DKE7KzZ|bw=Ioa|Gjy1vrz3 z6GfqaL%HoF#|wSD276iFtMBNLc?s&^d(HdrvcN)E&^8)wHSFgq<0tQRn*3GiV8JX?Tw65y)X+SqZi%+9g+5P#f1RuE@v zL5wAE_%0`g9brdwO2IIjpiarjo!I_ywk$V6ylvOWaAz=CMPM$JxlH$;(VuLJuIu z3d1O{3-hv%leYGPD2O<{(2P7APt zp6~^;Bo}xh7q?EC78*(>Cks41F^3F?bTTW3BOtQ~j)2RK_2&pUB728)2Rny!E?X(6 z7K|1!rNH(MxB#@09Dy5$Bk=a-aKHwNoyjM&NZSA|n>&ughm+8C)oi?!jgxFHJ1C2E z%gQ1_>gM8HZhC%hZdO)$N(z~dr>Ca~x^hFvFivPPJ1d9d!^RyvIbLrBGc17}5}0TE z#Zq@P=)*|UjSc9UF%G+Tqc3MF)}76-JGNYQbaZqEI|_7f1o;p*xy&=6#bs~^Sh>=B zZThYV$RX~g8_Nj^OytccZXq03mV=*n`9ynndn*SIZ#LfDr)ySW%=?F0CFeWOBV?RE9_bhCAIv1Q?b zfo`$AL@S#2XXb_2FyI{(`1T3nw;gr_m+R!{!e+q-Ky5mTdz{820!0a2=wEoB${MYL z>QjWSu6DpX)@(a#dJPm469E=H5o|jjU$(Cu>EjEw`M&P%K7qbL!4VO@uEAVR5LoQ^ zcv$0ht{k_vo`6gAM3ia|#k5K;UQUPmp%!a8)tDO=j*a+uv%@kgruq3;95pC5u(@1s zcgOv!{d^t016=T~UZLS6ahK&M?Aq+bPY$$ee&Fd(g!;pTW8>!H8S2@*5BG?S1N%od z6K=_p_{}qJ1`1<1K|wCzqS$@-oWQ6Kw$LUC=uOyXz+SaDCF(;P+{Vh<8uPKmZEW!( z9Jj?WJcw=U#%9|BW4Xf<3br*K%x1gT8Yjqu;D4AP4xvBSYKbampy{Dl_#PcRfIgeR z1JLB?$~@N87aw1!OzL1467s5)v9$TSygspBc0s<(Gp`f@NqJGtzcFXJME6X_?cnoL z=+w@lkr86wfWc#gbL(<3w-g&33--5mPrq^}wyzm^E&_5%0Acx4^o+>UVKBeEb5BZi+eRbT}p&9;d{%UW1}X;XC44 z!S>!EKFw?V!@O*)J;9XXX7PE`7{35VroX?1GvR}O8zr!{@dc9BVmN++ahqx7kb_}O z63l81W(}6bad2i!#xScF79+4?tU9Gj&z|)`8V zZ^?CZ>lqj0XqU$k*cY%f(|7^FRvp|e133i2VSqJWG+a`n;*kf1>HI zbF5QSjOpnhpd1XcK(l|S7Cv(aAGo#zE(u78b&gMHc?~pm#}h38S|0GF-pc1`_F6vH z3eV1pyfYnw3!Z!~*?zv6%}L2zcUwDFx7dsbx0jDc_=g7v3;gvgI|tW_et0s!NnaZnSlF-IvgR#C z0d}qqRzX2*S3g(yZ`e(bB%^$M@|%4Jx_de~1_im=cJM92Fsug+>+!DdfWZhJyc-ys z8Jk&t7H2HZSgx|#YrO}8O*uORzyUk=7O-FIU~rsBfzz4R;PS*R%w6ko*Xs|TBHvS- z5B(nogar-{8WQp}bXT}d#2DVe$fu;9zc$ueuqVMGu_5VY7sr1)P_>1wTaq9Bz0hrg z3GTK-JG4VP{B20F{7Zmnhj#cEgrpQ{il!afp&i&5t(F0;&VEHE#|ExK%p zahXOsR@e{|jx|OyNxE!&D9!-~-7|-2< zF0;&VEHFFwRJv@5IlJf4Wh<<+3C9}qay>^1P+f062C|d*zmF>*h9bh@a4VB@GCmbSzP}vofT_R#o+06{! z9qCAwJe*j@WrScj>-d3ITDpMuxxE6zqJ`JJF|8>qb^G@ z0*k>&^dT@QR*osbZ!V_AG~i>vYOyNRk_KuzP=?a~D&HK0QPx>`U}g~>r#4$4*F zn$E!?sa4YF-#)L)4(lvpL;2`#Na-vlj@5tl&ide8#xS7q{GiyrX00Z5?&J@vo} zQc$WyeF*dp!y05!EohUWew1D^BqfXq1(E@3QGxU={fFYIEoRc+lmkXu8L*lf@g}fP zKtqM})S9dth7}=;>P=cF0A3`MgPH zrgf@NT><()>+V^t2QSJGpoWsO6i8BI;cOsG-Dcl#EDvtFwi9e6v;%ti_{%cxuBnKyO( z6M887s!%^8_S>@?uT6F{+P-aXHCiXC+URYRt7Wy-GHP08BBKv^DBo)lmr_$*X%-kp|!&6$C&or%L zy2TPT$c~l&pdXF10bC7gXv-+gBv7Dl$g{;Rg8+uyZ0<1pNj|1BzXEAsobi%cWpNpx zs6`p)?dxl5?Pr{2TI^$-$y#h=o>N-)(xY5K`KpB0`|WeC>>VD}nY7o_D@p?zziK1_ zyly_D|DzFLoZqrA5xSa>Wq?{S2H$K7Te3kL0kWtVTqy#zbkLd(S~>y@34OgI@>DUJ zhq3@(A)4bT8e;IB2kO01j?2IZREM=506!1Vf_+6;FT_Ixs7Vl}7}3rJtvTRVMB~98 zX`rPL)L}UjWkHHx9_U5w7P9F%k&;vj+6j|}t)Uq(cU{PU#{-5*-nhUDOtp3)LPrF7+r6{qVyBe-)YEl`KU&DBMsH0$XDCu*JF^WVH!V)5lC8h|;K+#J{Z;VcgN4}XP6hG)&b6Jd0P5j9ip{N+I7t)@7 z(=O2JLSz#tm&C;RU6iAiz*mQmgtS~qxVwsj3pSN-k3qG)I7=1xyLO zwpu4wf}c`DjZCK`sx=CwjxazyvZX|hO0LxCm7NK_Qb{N)OOy(QQbDMxRzjiF%XO+M zs378@P#R<^wLVI!s#NNUJitY)t(0jXFBxIb$rQ>;nXZD+mcH9g#>S+!G{nlXYPC#9 zgyyQ`IxQ48tVpTTL)HmVBp;yzY=m4O$(5R@BeSOKWHl;H8IfOF3S=iD35h|bQ7dbK z5IPmG8jmPa$qhh-9GOm`)EEeUVr-0w6QQrJs#2?f=A~MVA&Tg&ttKjEwL~?rfdN_@ zHWLOdA=fEo1|^SBsPt9913W^eQ4m!+6}Tz~D3GvBPgE&&l`4Y)kSnQ0mNwec04@Nx z=!}I@$bkockv~l0ROz&eYPo?&z&HYZd9aU>fl5Qvl&j?BW|GwarYenGU9EsYXOv#6 zQP&cosxZp0X6S&zpGixF0ko!0sW<3=-GCEX7{Kl(TAh&vLREl^L0JjCtWyE53T=%> zt(7TSTTe!r3WOg}M+;bi?`lI82xEm3stIw*mFlY276n5~Q%j>mhX8`WKIN(s6_7c~ z-X4ZpsaCDlq5z;x%_B-=dLXM-V+wVn6GF=khN`6KXr(5qMpdDzQYuukD6Ot68rGr# z<^Xz7gaP-Wu+T%nAepz1;w=jdXka z0_Yq)8U;XaU;-uRRi={x%P4q6sSb=<5Iyp8nXU}z3~dZ-1w0D66WS6mS~bupGL&hI zarXCX0R@uj^;)?~2IE7al~-2+kI1Nmpi%?NghGm~v?U~TsyPvcBvL2=K^5hPx8e~s zDnmJJHVZc%9d1y1<7Kr9#3{ua($Z1M7H~j=2x`b9Dzyq#Dg0F;gH%-mh4kfUzyi`G z)o}3YVIv(SK*MODxLygeH=qE0O<5Pok_Qg5s+LHhFX_u=K&ujE ztC1@+Q&b1p>p`M4sDO*W;H3uopBVs-w=5AM$CHNSu}hs>0OloNQ@t9+8nP&4V;E08t@P zAQVfp(+YEhVxpi>T#zpj0ru&DY+iO=h8S=W<%;s8QGhFGBSb}@LP)ZNIXQ@xunto)pG5ok;m0m+1^IUF?(fR+SyHb;&EYaT7yKf%3>2JKTyuv*+6>_!HG`faq6 zGF~^HrYopZa^?)?YUWnvHt^ZR{EoT2{gk}@l>EP8+xAoPzwVTry3^TyQvUCGQciiV z{j|LOw7mVay#2JiZ8mQ|F>ifhZnRGOsd@XUdHbpPzu2j{+5S?7<_Kf`D%x)I+d@=@m2UdCWi8-5a8?3vultB{meLqy$%4_ zZ~qUce?#FD+zPd(j4pfVsd6&-aD}=`4Ub5x)v0+zrcPPGBXVR0jZh~m0bBGpuEBFN z3Iox{5fAX;rh#8KiXFEz*}zS(v^;5Cg~gj-w+L3C0i{rB%GX+A&~qw1X;w^l)CdL zuXB~Nt%mfPd?&JT^HjG^A?)2_%Qpva7_+wK_S^>BL1Y8#A=$t@HTOj91zAb5E$t+}9x-tqSd{@#LR;*pw#WH=lN>gUgYWPm1 z18lT%wGt~8m0FF0&mnzb3)|Jb-6K)z>p9P?E`B^}5|N@?V*hgA$h%*7E&6`&CZ56jF8g$bM1SsN_`r|8 zYY1DK6!o%kxce^K)#XR`o)s=7X8qK8)0q2PYv;ch>(q33;-2pWiNcp&mp-Tn)+dw~ zY)@?}dwP5J=9m2g`p;ypIKi2B&G6Be)p5smSAX+(NX4YZv*+8ta`JURfMjcJEH1g>XQS?}BYPRWP?99p~do0W*Gx?EZ1aye4Yfy_rvUN%|JSNSrQc`Zw z6FKlGS*hTAkZus&!qq+|HYPDHF+va*C+I|y{*Y=Q%Zv0F<#zMcfZ?0DSg-54ymgNQ z41qB>?=L5dAc8+Dp9GBOX3x!R6w!ATX(#`oL(jY%wxDEl z>b{N#=T#PkFgK0DOPkirvYp5LzWDO%^n3T_4nBP4;F7~k>rqvkPL9|d*qHiisboar z^?TaE*VMB`-VBY}VNE0d)~F+8*SE-P8&=`HCT~9f{=DZuoXFVneB#t$7siar-}lAS z1LKDFod5g5W#2gUe{ijQ(Sa)u<~>|JZ{n#J;Uib=e)-Db4mNK0!8*?Ey0V3LzMWWn zF>}L`jZZIm`DZD<8fG6I|Cn`hU&4VO>O}T;?nd_x={{lao%>ba1RZ&v^0Cy*%kZ^D z;>elH5BuLa=U$d!;}ia?Yf)fLGTC5R1u|bRD(kUjcH$iBPkP!WyZW(zNj3o)i^?Wp zE!V|bEkFXkRh6pbGJ}#3RvXH-I+dZeCGU}B0vR7e3P?W6dvR1X4p#r^dHHW;z}pUr zg4(imyRS9bU^#Q9E`L^3ekc9xkv+ZgmwYwk{YueyClWvXb|L5ab7jMI&*PJu(q~!U zQcgL{EBtWV2*0To!#V z?)zlXxho&pX4RY?oq5V>)81Q4fBR@qq{Vv!_kJ7RcUGY90MRucH;y*pNAK8CgU019 z7*r}BI^`63ucS*aHjl0j`rN2n6l`M z!`gtb8(+;+P2f&BXqf)Xyv($RlI7bSNmX6*@nE7-D?4%7>eNp%ZBo%anV*Saf>+qQ<*lJEhN*uV14YXSpwK!&x4#|fcw$|D)r*B5EPQcLnf^#_{`yN6Mbm~o zyDiRrTw{?xdhc1=-zVKmwK--#KYS89U@#|T z<#go_+*_#u!Q*`+2mX{WPB<>y!|{x)%hd9L;`^sFznD8J z$tjI>?U?8}bWHr=yj=sYME# z=GL!(X@?GE8z$e*e2W4kX{p)r9Z2)5?qqaJM;4#UBA!3#9lwH;RJ``ifo!|Y$Lu#& z8eR)J{QC6SV{zlzo3VLhs#$kCzJQFG>o&^e&6{cSd`5Y_Yecu1e^?DTHo4!m%a;eH zPCQr8E6-A1=QTv9IW7NfRbcI0)~>-B&(3YFsL+qc6hP;R?{juxCCDVoH|Gb7JSBAwc)bo^Wg4Pe*I4m9qDrQ zV83a7mt3CoWA23D4te~;0fi$vFaG0RpyLPq-6lT>?cx)%>e0gu)4qB{9CP?`!PTR$ zAD{Una+kqr_XfA*-}hWQy{uvANW~c61U2u-j|rFZtBLc~tnMTbV~z zpX#USdTdP8yav~GWP|H!lX00$oZsL&546p0wKH{{1e(S(aORsk(*|5(X=`+*6WE+K z;Hkj2U=@WYfoP^lf7qMN#NV*}sQm@yRJ(&hWWAGA1Sgtk63N;-X^ukgQP*{PEoSlMCyQk@biFk`WU|cBJ+Z zW>00A?H>xN!Ca&z#45R#A3_FEsD9FN8GIy0Dv=N(NnR3P&?zl4O^})x8AHa$^MlDi zY7qLikc3aQMM?~^$|^!aJ?b^L!I2BrpijW=`(n%P8@H6LOaJ1JI}h)ngA2>H3)1)h z5+l%>FHT1dtBllQHORrU+Xg@ zY;?$P^C$mutFKr0S+;#*XU`IsH>QgMQYYbwhGqjk52sV1M$jg6Oj3 z@z?4jEypyU2)Z&&vb96NqGhwh*6;OLHS6YkpLG6Y-L|pp(YgVr7wgjko;Ui}CHj{* zhJ_wZPg?$KaeUgurh7Amo+qWR`@FngcYKW3oC}NU&rP3ssqw|iEk`E4|Ki}_S&>7& z!LPf^4@XUvTlLO==5*~s|9J7Hxv|z0_D(L$zyImr@1|Y2_vppzDE5rV?O&7`7quOL zMLf-xu+LAf`)R<$;$K!<^bWBR*xbs~p2%sv5|t;tH=h6TqqO1gXLQ&TTi$1MW#RvH zB?|lpyw_?!%>Un7iN1q&+r9Q%4mPacwgI2?;Wy>$&ez{LKj7u7W!GP<-5)dm%)@<@yKl8y`(%Y`>YZ z?S4b#?Q`>2F8aXFLbv{$aOjNu`f0yjI^C_%KPu=0_j4ackDTyyy2aI+kyxjp+?@;6 z596ly;ivC8zH1WOf6l`p>&VC>Dg6@8&0YS~FMmb%n_>L=Q4_WgoDDYMgF*7^ZQOv{ z)Q3~=*Z4Qsfb(PcvGH+n#tk^A3Sjl0oSOeurdr}>zoo``d=hc@mFu)8FX~iND-2Sn zQ(v*MhS4wYyB!>y3p@CH=6Be}b<+DXviE8--SPK< zV?UIp*`3MV*vWcr>cuI`PwUCzkcI^!`=fWBWY!g8LgJmxsdGP$hoN$Gax=jkKwAMMekxiRCw>aQOT3up5moXfNPWc^s{xLZSg zSKa>NP?uk#)4yB%_<~~VLVO2dGiB(ZS6jCKklwGq$1L0;pPOsUR5yV|e*V+#_ghm_ z`}Vul_IobcenU0?jdtKxH^=t0|9S6~>D}rFGrP`SFfTy6_SZ!RpYXpwV)65m<`>14 z-sIYydrE&8bot3I4~A~t_w}6MYtOgbtB@93NAzq+cX_!L_xFsO9#quBa6i>)|H^H3d+Q!1jOf$N_u&_{-9HgyD?2%QK8dcaSN^nQUB&f% zho2r8hG~0{=|S8sNA5Ad`h0Tlur2GGSXZmQjOpI7sc>a<^ZFZ;VmFrzm~p;1<@WD~ zzbO59!s|gLbr<(^xBVpK=fu0--3QBO4-cr^HEY5AE-&gvd_L&op^GYyU!C>b?^UO> zQy=gBiv4-x!mV?BRMztU_C}r2fanHp>$?-`)G;*@ka*hx{II9F22Ez3eb9T$;G1@45Z< zg%ey zfpaN&VzVKp$7T8OCtr+@?yxUlW$$$>E~IyvRxtR?yyJTcucKge+}?EXX6{S- zZT<3Z&*pe{ir#XhmqpZl`?&>i=kD8d^gXq9WE{u(yL0j9PYjrR$uKtCWnKew71_X; zj&|V9^Zqlp74w7HKs_5viwFlM!v{pZJ0{=yhYzQ4^q}87e&2j!)s`dDxQEZ@)?Xs) z&o8V$OV*$IXB(g+8BC#aG-;|T_}3qKQpy5?bWvg?nHDFCj1`fI{0K6PvH-WW1qccC zr%$;uJ^Y)KG__V1}2wl8lRwi|u1-NTeIc8j!tAds-IG zj|QoaO%s4q1si-Z7~V%2AiZ0mvJZ1+gTlIOBXYuJrxHIJBQ zR&FCQS}5lI=Cb~i$l)%d=iqZ+ts%!36xtui+^-JF9W3p${sTMB#RSE!Jx*te?gh)L zRC~YrGYwHelO1}x?OcC!Q`H^U;{=ViLsS%; z?k*kU;4yi>Uw)W|6Pgz3X@s>ADxs@M-b)kvl9#=_G-<(uKZZRWD{eB!5BaGAjtY20 z=5fkF9rDA`GJ!)2NI7-0#RA>1RH)E&_r5eCO|s2$j_#i(r_J|%-uLI{-uHUH_nf9q z9NcnZaNg(4>1ChnSXa2GXYHzqb2oQPimYeLyDI*0VYj=pc=3X5_xC^f`NNO*?Ja1p zxWDGzofC!JA8vn1TygZ^KUyz#if3NU+qb)``|Z`o`<~%@`_JAw{hk5)$u?@q1bp!B zHPzbQQ*wXLE0^|dddTs1&*tXhm%FA{8n&1t;TCrMgGZm3x25|nX3v`qx3(2{zPY7k z$Fi}1YFxbIxuqu0u{~Z6EH-)fE+K24CA2jQZDSqHt@TYS*!gqlw##Qud_A^)TI?Qg zDSO_uh0{+xx5T z5bmy;^dWJjr^;=poVu%F!Rf7sr#Ef2+!V9e5`Z()dTzcmQ`+?vuMc^Yt-An493oKXRmy-{n!e=`NX`) z>QjyN9iOdbel-8rU6mClSDk2iET+9Yc;vZ1Z+NF|O8aiAzTjN_D#5IniDSW z`)Y6fD}yCnW}I2I_0_K{k6Rl;e?0KUmx~$;JZs+k-Of7)H}LPD>-f#3g;T!ZUUOUy z;HxjbTXSG$u)q^}sB5672IhYOCw#!ll^kuR5Rp)O92r`&n=6m>E5-;r?AU9WQLOto$*y@tEoK!G)ic zc0F>YV9X6OD)ZWNRD6I7$rA7ME28LnGDk(}01gz%0dNP<>GTxDy6{6u~bhJHw$UinGLF!AKIgw9ahq#nXiu8HgM3J5)^q*wteKngGV zYrG6`0fFpqbRE5s-~aqZmBiyC9fh+vONvb*S6pEv)k}h<1codVa{M!uBa{OgeWtSs!35m0U`!Akf*mFxu+i8E$%jw-SEOw*!4r@NY$ zfs3#51@hJZd~dfJ__%!aT*ijmf(5+S_eh<@m(@LLch^=Oe3d33tOu$-?}gCW1c6u*Ix zR!L0j7X28!fDu{>Lyn+qoGpo5*v(63o6imZgT850z*X zoPnx*byUOg9%opP)n*H@^HCWTMrFzAR(P@VAsJXjcNqCq%CVNgdFJDZ3qEsPP&{MT zqCv?GA4;+)#RHb{zf43n+niE0WJl!Ed?-4|N7C{-$$?8M$M4W~4u!A{UdiZn8C$QW_lgr#XJmNt>|d1}9nWs2tY zt{9kr&Gz4E@%t0Z*lfncoX@nH!(kt1wCG8aE@TM}SxD$KbdklN(NhGYu`=+>XDqZu zH(gICWe&GRg+gRzAS62jZvJ|_rPyXE9()fk)%Oqsj0PDI%cFFS(lsImMx;Etx<=_5 z5d$Mq9$j4{qbvSrA8z>JZZp|2n6Gslum{dpM_@mZwH>BWN0sPntiDiCaMr@zjUDZa zfh>i2)W$#;iV97nC@W1+c`5I~%6xS^pb2cQ0}y})u(!oZMNx(W9F$@+10E3&s1<~= z)nQ@@x{xF-dO}AQS`2g%+9;*&lg$5H0|qGa2{JEXMFQMfT6wp~@rmAxporAgE4rFF zv@7>U=eWfkz2{wnZ=8bPwBOTGICb*8PtV-aP<+?;mwvIiVqGu!v1ujO`P00QyRVE} zFy*PoKm5zDmLKR0GW`##r$Iuwr87q7G2c`FZ zFs|sgmfV|vk3aulk#6D9ZOt9R(pjP6=JVR8Zupn>E-5SVQHl5Ds9^6(!rwzLf&nbU zPz*p<5*R$MKrawTVg$=(U?ggi)AVQqla3L33eLoEJrc(=^x$?D&R&`xX~?vt=#2bG zTAyjlz}7$AAFIpsr=#IH#PGNjj%U_}6YwY{3s;$#K8v0q6C>e4M7lqc$b!M6pA0=J z3r1(bvdk};r4CY;#WqVFWR^OpEOk&>>Y%dJ0gsz9YJ=+~1B0tE4Wsn>j5(r79elV9 zn@fhE@$@-o2v(no8FcCEg<(hr9>NUw$55&MU}NqRczD0TGn)8AiTuu=MDb^hQ9.4.44.v20210927 2.17.1 2.0.24 - 5.2.2 1.18.0 1.7.25 2.3.0 @@ -1552,26 +1551,6 @@ fontbox ${pdfbox-version}
    - - org.apache.poi - poi - ${poi-version} - - - org.apache.poi - poi-scratchpad - ${poi-version} - - - org.apache.poi - poi-ooxml - ${poi-version} - - - org.apache.poi - poi-ooxml-full - ${poi-version} - xalan xalan From b86adf35e233940319f2fbe44b6833a3f0016e08 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 30 Mar 2022 10:55:43 -0500 Subject: [PATCH 0820/1254] Remove duplicative dependency. This was in POM twice. --- dspace-api/pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 2c998572f4..dd86890435 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -872,13 +872,6 @@ 2.0.0 - - com.github.stefanbirkner - system-rules - 1.19.0 - test - - org.mock-server mockserver-junit-rule From e9e4cdbaac72da5086ccc593d656b38751224c92 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 30 Mar 2022 11:20:23 -0500 Subject: [PATCH 0821/1254] Fix dependencies in overlay modules so that example ITs pass --- dspace/modules/additions/pom.xml | 68 ++++++++++++++++++++++---------- dspace/modules/server/pom.xml | 33 ++++++++++++++++ 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 2abc12b529..569ca8ce25 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -254,26 +254,54 @@ - - org.apache.lucene - lucene-core - - - org.apache.lucene - lucene-analyzers-icu - test - - - org.apache.lucene - lucene-analyzers-smartcn - test - - - org.apache.lucene - lucene-analyzers-stempel - test - - + + org.apache.solr + solr-solrj + ${solr.client.version} + + + + + org.apache.solr + solr-core + ${solr.client.version} + test + + + + org.apache.commons + commons-text + + + + org.eclipse.jetty + jetty-http + + + org.eclipse.jetty + jetty-io + + + org.eclipse.jetty + jetty-util + + + + + org.apache.lucene + lucene-analyzers-icu + test + + + org.apache.lucene + lucene-analyzers-smartcn + test + + + org.apache.lucene + lucene-analyzers-stempel + test + junit junit diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index ad47074392..dd854c4215 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -278,6 +278,11 @@ just adding new jar in the classloader dspace-server-webapp war + + org.apache.solr + solr-solrj + ${solr.client.version} + @@ -324,6 +329,34 @@ just adding new jar in the classloader mockito-inline test + + + + org.apache.solr + solr-core + ${solr.client.version} + test + + + + org.apache.commons + commons-text + + + + org.eclipse.jetty + jetty-http + + + org.eclipse.jetty + jetty-io + + + org.eclipse.jetty + jetty-util + + + org.apache.lucene lucene-analyzers-icu From f02d8ab04a9fa328306767de9db8e27382437ed1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 30 Mar 2022 13:52:44 -0500 Subject: [PATCH 0822/1254] Add test files for major text-based formats & test to verify all are indexable. Also add CSV to list of formats & include it. --- .../TikaTextExtractionFilterTest.java | 152 ++++++++++- .../org/dspace/app/mediafilter/test.csv | 4 + .../mediafilter/{wordtest.doc => test.doc} | Bin .../mediafilter/{wordtest.docx => test.docx} | Bin .../org/dspace/app/mediafilter/test.odp | Bin 0 -> 25579 bytes .../org/dspace/app/mediafilter/test.ods | Bin 0 -> 2886 bytes .../org/dspace/app/mediafilter/test.odt | Bin 0 -> 5653 bytes .../org/dspace/app/mediafilter/test.ppt | Bin 0 -> 96256 bytes .../org/dspace/app/mediafilter/test.pptx | Bin 0 -> 37911 bytes .../org/dspace/app/mediafilter/test.rtf | 239 ++++++++++++++++++ .../org/dspace/app/mediafilter/test.xls | Bin 0 -> 23552 bytes .../org/dspace/app/mediafilter/test.xlsx | Bin 0 -> 8466 bytes dspace/config/dspace.cfg | 15 +- .../config/registries/bitstream-formats.xml | 9 + 14 files changed, 411 insertions(+), 8 deletions(-) create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.csv rename dspace-api/src/test/resources/org/dspace/app/mediafilter/{wordtest.doc => test.doc} (100%) rename dspace-api/src/test/resources/org/dspace/app/mediafilter/{wordtest.docx => test.docx} (100%) create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.odp create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.ods create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.odt create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.ppt create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.pptx create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.rtf create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.xls create mode 100644 dspace-api/src/test/resources/org/dspace/app/mediafilter/test.xlsx diff --git a/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java b/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java index 5ebc85c956..b7d0ed5a3b 100644 --- a/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java +++ b/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java @@ -22,9 +22,11 @@ import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.Test; /** - * Drive the POI-based MS Word filter. + * Test the TikaTextExtractionFilter using test files for all major formats. + * The test files used below are all located at [dspace-api]/src/main/resources/org/dspace/app/mediafilter/ * * @author mwood + * @author Tim Donohue */ public class TikaTextExtractionFilterTest extends AbstractUnitTest { @@ -94,7 +96,7 @@ public class TikaTextExtractionFilterTest extends AbstractUnitTest { throws Exception { TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); - InputStream source = getClass().getResourceAsStream("wordtest.doc"); + InputStream source = getClass().getResourceAsStream("test.doc"); InputStream result = instance.getDestinationStream(null, source, false); assertTrue("Known content was not found in .doc", readAll(result).contains("quick brown fox")); } @@ -110,11 +112,43 @@ public class TikaTextExtractionFilterTest extends AbstractUnitTest { throws Exception { TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); - InputStream source = getClass().getResourceAsStream("wordtest.docx"); + InputStream source = getClass().getResourceAsStream("test.docx"); InputStream result = instance.getDestinationStream(null, source, false); assertTrue("Known content was not found in .docx", readAll(result).contains("quick brown fox")); } + /** + * Test of getDestinationStream method using an ODT document + * Read a constant .odt document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithODT() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.odt"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .odt", readAll(result).contains("quick brown fox")); + } + + /** + * Test of getDestinationStream method using an RTF document + * Read a constant .rtf document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithRTF() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.rtf"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .rtf", readAll(result).contains("quick brown fox")); + } + /** * Test of getDestinationStream method using a PDF document * Read a constant .pdf document and examine the extracted text. @@ -163,6 +197,118 @@ public class TikaTextExtractionFilterTest extends AbstractUnitTest { assertTrue("Known content was not found in .txt", readAll(result).contains("quick brown fox")); } + /** + * Test of getDestinationStream method using a CSV document + * Read a constant .csv document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithCsv() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.csv"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .csv", readAll(result).contains("data3,3")); + } + + /** + * Test of getDestinationStream method using an XLS document + * Read a constant .xls document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithXLS() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.xls"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .xls", readAll(result).contains("data3,3")); + } + + /** + * Test of getDestinationStream method using an XLSX document + * Read a constant .xlsx document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithXLSX() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.xlsx"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .xlsx", readAll(result).contains("data3,3")); + } + + /** + * Test of getDestinationStream method using an ODS document + * Read a constant .ods document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithODS() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.ods"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .ods", readAll(result).contains("Data on the second sheet")); + } + + /** + * Test of getDestinationStream method using an PPT document + * Read a constant .ppt document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithPPT() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.ppt"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .ppt", readAll(result).contains("quick brown fox")); + } + + /** + * Test of getDestinationStream method using an PPTX document + * Read a constant .pptx document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithPPTX() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.pptx"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .pptx", readAll(result).contains("quick brown fox")); + } + + /** + * Test of getDestinationStream method using an ODP document + * Read a constant .odp document and examine the extracted text. + * + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetDestinationStreamWithODP() + throws Exception { + TikaTextExtractionFilter instance = new TikaTextExtractionFilter(); + + InputStream source = getClass().getResourceAsStream("test.odp"); + InputStream result = instance.getDestinationStream(null, source, false); + assertTrue("Known content was not found in .odp", readAll(result).contains("quick brown fox")); + } + /** * Read the entire content of a stream into a String. * diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.csv b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.csv new file mode 100644 index 0000000000..07c22ff0bf --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.csv @@ -0,0 +1,4 @@ +row1,row2,row3,row4 +"data1,2","data 2,2","data3,2","data4,2" +"data1,3","data 2,3","data3,3","data4,3" +"data1,4","data2,4","data3,4","data4,4" diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/wordtest.doc b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.doc similarity index 100% rename from dspace-api/src/test/resources/org/dspace/app/mediafilter/wordtest.doc rename to dspace-api/src/test/resources/org/dspace/app/mediafilter/test.doc diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/wordtest.docx b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.docx similarity index 100% rename from dspace-api/src/test/resources/org/dspace/app/mediafilter/wordtest.docx rename to dspace-api/src/test/resources/org/dspace/app/mediafilter/test.docx diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.odp b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.odp new file mode 100644 index 0000000000000000000000000000000000000000..4701884a8a62eb650dadce702c25c74b2e1e7448 GIT binary patch literal 25579 zcmZsC1C(Ylux8tuwr$(fwr$(CZQHhO+qS0dX`9>g-oCeQ&+ea-`jecSs!Aofb?eK$ za+1IxPyqk=BLH9`ttzAYsr<_jKiS&c+Stk6&RE~h&dS_S-^tw8hR(&th}Kr$(cF>N z*3Q_*$kx!=+Sta4*3QA$@#p_Pu95p;iVOhuA1nv}{8RZB`CtJ6@QVQepnsSe+S)k% z>)~c?b*a5+bHI-1t*iIVUpdS@QGXz5K=hasce?em z^lz$Fy;wvN^(*Vu4zUSt2U5e3r1I@e9JRbGZ%-A{b^0Nw@!8U~b4QQpLf9~~w#=t4 z?>CX#Lw7)^IW{RZiK4G4)c5Fm?)SqGDl}%i_*B7kMiF(kzK7XV*W{o(*-h7ZT@iL2)(l|bJ zbf%u7@j^iM+l^#A2sD_rwkbjBebCyl5eG${avXt@y@utb>19I%c_{{OGctv?yN`5Y zVlLQv3N4CPs^7fK1vHc@<}D15-G1ZjWD4mG!jhK-1GyEOe`0E4Gr7@UQ(eRiAM?9` z9XIZtL9M@j3+b}f#SY#Ne2<+h0dHSY@G*9S>zE%DP;#xqm*VH>-@*EzBk9xv^nYy5 zBUd8xNf&~?Wk2h9F$L5a#f`w#;eZKGl8|=-q*3Yj15W5m^`ST=)O+BNbdB<;X9t_q z_zK1(AWej*^3}w1L#Fp^k0pplF19P=w4{ znP-XQ-!s_HKMS$e6qAi}N?;mm0>#;hw@y3+%uVH7+@^`5$yh3r4$O9lQ}ySLwpzd|;$`d8 zaCG6QpawSwW)74vv8bBmPPs(KJBLuX&um|DyrDVXjfWT*npWUzZXrZkWh#E+wpr}3vUqDoDs z7ia}*D_$-_0XhRfHzf)b2`Z!y8HA%v}(H z!N13#83+M;msrjLB+Dns42c_)c_@SVV-K$bOc+skHAtB;57Sxj8CJb%*ve9~f>KSu zu-G7yug7rIEs0%`gfiGk@YMatW48~-E8q&2tB1rCNNtLx`smd5N3NW3Enur;vDD&|n zO%K}ve@+SEdBIVJ1lyowlc{tM(}wUN*53Cz5MKy&QH9EHl;;E8Vv%0CMMAC7nBqVM zfUO7UlsBo-{2&M)Td1n&k$9mAC`>N`!HoO_MLHt322e&9;8fYa;Kc!|mPbeCkBJrg zMyw|tM2iDXwTs}KOoljlVfc1hgxqFCJK<8DS<3`h-Qu2EZ!sA#zqJG#A}pqF^Cq(C zB3R~<8T))w?>DJ=Z&2~Ul&)Zzuo+FkH&44SkvMkhVmwi}sQ5n5Z{*7x`rlT`b*K6i z2d8_RLOYIZVSgW;aoj?;9mF;aY%t(7HI`96Gu1-iJ&QST25^PfT8v-Hz zlk4nSlI+<%vDh_f>!10ada#p=qoV6dsR!ve!zybE9%EW0g#G49?QDMW%xg|u6L0m= z3~Uo(7cg6hGKQk=3I2j6PJRlR*;!$ZnQX-NH+K^_iTRUZ*c1}DFcO20OA-bvX@^#5 z{FDt_Ex2U1=ERD4ZFzscMdBv@{#2IF0Y+!s1I^;Zpg~LBthJVr_M77~6@&9hK2&eH zvHHjc=D!+@K%&r6mmcHyd z_6i_aLXLMWI?Z!HNDGNgv64Zl#)zbFiS?LL(8kI%N`6Qi!4cF8uFgA`xlQ8QLLy)z zYcWq+Sd~pLxe@b z65K^_Hoji8ghIC=1JrJ993UJ=y~5rKp5J(Pfdde;}Qu+%6gB$V4S_6gzYAcy{%$2L+YKs#CIEU!;xW* zGeMs0f2UI{>>;Y_eo-_An4t_DRepbd@*Y7qsl`$cbrhmUBD9_*{7g?InB5?yPL1E0 zrMN zA$Pzm=58z6?ggqdoL}lugr+yV`7uoQwyvx#PS@zG3G-@i#_`QlM&|tHu7^h_LoAk# zwBz%>N_ysP+x+8$iy`i_9n18dbQs%#{f$1;vaf0w9m zU1`m6!{@X`GbXRUCeUKGWbQ533tfxa8M|b0%VU^zIrG^UywXQG7QD*;k^e4f-L^T3 z-^+FRdfX{p4lq$p@Tl9;avv=0{S~`6#yt_cFaAgVKPAWEgD3HO98GF7A=15&MwTuo z9C2y1MzUlE)7zR4qDk>rho?)(YtyL#lG__L42VyPdj)ybwJKL4y)Xx%NtfXrS3CfS=Fh2GW&<%JPL2*i13I{fUfdD@Mmz8KiM(-s`AsTJ3^X3Sr{ww^4Utt>Fl@29m!Q5G zmdol`H4@kySynn9^EuPT+cSzlL$GKus&i7m>WvNwU)IF8UN7go?C_mNu@_@&o>+0G zys=)g6sJyO{e+aHdLvO=V=8K{c1p(-26ZEo6f=tBlp6+5G}ambII*;?vwLMF@m3f_U&45` zLlt(hD60xyn%1sWDREqBqdkn-EM1GIHthKtHoC{{KFeB>9nR<0XS0e4(epC>5I6LL z_@m?ApvB4_zOk01)958W$807uR%VdF?i36=Ft&K|aMjOnV21`6(8^N}zt5ECum4Sn zy%hvREUrQ}6rGddi-(7zIeonZ85}hJ42@4j3HvOHW)(QdQ`}=;+sp2IQ2ho*9|Jnp zK$e&AJKJnTBm52O$wr}iy*jW03H3A7#Ba6KrfKsvVwlwLoIKEK(b+N?7{P9Q57;~s z8*oc|&$Jx)w%=Mu2A!XoU^H#NFEV5{8!%r~&xIkkN|IWptIR?z@mGK!PU6m*fO}+z zD!P??VCsjSh9422ZF<~qmLCvm>+3<0ax1P7%qE^jc7(z020k^D3UR z6Urb|6bkql85u9idy9FidyS^I&(IW{z{c|k1^2@NJsOCSggHJPF4q_ub-xZa2|C^} ziOtdRR*77>F}6WRt-GLOV=v};zg{vMvA2uUeYalaynBQ?h&v3M_(iSR1A5SSJW#Dspy1TlfbW0A{-9HYcNw3Hn>;h^Z;+=@A zk-m+8d&tDV*XM?u4bJm|oC&sghspNK`}7O?7&jDqdhWVX>THZk^M24oXL(;*9Sn}o z4oD-XrE-w)OSlT#*2SB-upOIDSr&)VTX?{lShkb!ltkAK;_~g0_AC%tJ#j76#-?bF zd1026r0$Iz(-`%)R^5h_4?;de)$ehu2d-%vsti?1o(H zWm@aDW5AkR+ZEJ#8LpE(hL#g>a~ocB5Pd@XIX{`Zu_N%-<^Gqp1M2_O*`T%4qpU<{bP$@w4l?gSM-~nIC+5yY`l$F#09B^1w^rlx$ix zE-Urw^}uelaou7jN8#d;Af+M$>BMy}M%Brj<&MRb0gLV`_1-wOv+vC<&hvP=I@bL0 z?Il|VK!DWso9yPy%k6zXtqL_~E97OqKC2e>XnUbr4STWv0&4Op@_?gIu)EGY)#kv5 z+g{a*$;Uf<#x#-R9kluyDDuDqZM4e6{^J%A{%);}PJj=RY{$+aX{MGVo8N)uPP^dz z=KPzsPN>#3d`FdTUN?O)JILJ2>wW!*HrbUw@y^?B`*mKq3;NEaWYW`Fm!qbzI37>& zgD(*}NpEi`oBm*>RMp&{(N7&6*0w9H<_g1tIaY4cdujwJwyQ|ySspX4F?)a!+tIuA zGgjn`w8~K49?2FR>QZI3Q**k~m8yT?>4# zRzxc3A{&DH!C?mfaDl= zZ9HWEt3CWrK)c+5Ib(xvIN`a_usg z$nIMOq&dXf>Cwe^Pt*#F=w=;VvgOu#?AfbSMU*D*_h^6zZ|u2N=%+8oB&xN$MuY1_ z%E&DQ$bh0Xf^XZCq&U{-7+8g_zhd!Eb?`)pUd0UK9wKij3rfQF4Y;4onKWHA!!>Qz zUU#kfBVJkKhbJG+%#!de~%{-wtgr-*YM+OVmnGRO`Fp^F-nDCw12c|e}X~&j> z%ao=a*7=UEEYsq2bLT-BorhpXBMkmW( zdHNOKAbBR*z{lJw9xxhr){;f-K9iG0ycYRBOQJbKkIq}P?{hy1nV+@b~HON#CJYoV+e9l5*gGvBsr|KNX7>Pah+t= zseWJ;(hf!DN<{MyceJQ%8b8ObcVRptck-L92!dIxDo??^vspj zUgRCAd)%C)J}xKru+nk9_Fe#&4pQcmXnU#<8!jQl;hOJ@xMg_!Sto(skZK>{`gB#O z$=7NrZ9F&g+*F<=>TdoDJ!WH){n>4sJrPk?8JMjBiCVHxNq0J5gB`l zz%k<0#N6wlaB|;HwZ1#RNUS4OeOq>7l;`zQV&PG|QlHH5u0lUIXmdW*=zyrdcNAQH z={?#rZe8CJ7mm-5S$CdlP3!Px< z0h7@ZM`yL@xLV%zaO{mJa@#=~dlzcD#0NV6tocl^!Tf*&jPV(E96MI;6gAA1!5N4n z$`&hFfOVs$0qz&__`91aaF^5sVjr=8qTdGsjNs3^(TGQRX;un3mnR|}@==Q!DXOO; zcfs4sPx1=jxRazn?Z$pbd>r_}{7c-Hryc}>XH&_p<;5Gtlx;YigK3JpL)>FMxb@$= z;7j9w{-WQd_JGAACN_I%TVoxFztZd-NsTPL#^c3fCA$T3HNu>~i0^V4lN};lxl9}> ztH=&Hd^;MyQ(JHa_1+Si!H4g`)P|a&wWS~jjX5F|sQyg0mZ*o@oY^ntf08rVXQ_10s zkxU)X{s#3(l0H-G3bQ0Va$*L=ax=|yAz?4P1!}Hrqtqz}TCyQ6n8d8r=55wID3wo0 z+-R&BlPCJ7hnHgP8uaFHGepA3tu zK?4(1W(9y43Wun9_e|+BnYxAr0zjgob7#OoH(N_v_)VgS@S@r~O>j(SCdV`V2*-Sr z)_bRL^f3ecy(5GqF;Oy;I86Eopr4S$uRLZHH4Ltg2%U0D69yk`L>y(mfGYT>C<>^c z*n2{oaB56AqFBs;$FPQON^MZd4QaYY=a-^?9OGou$$JKJ-w_8sOhk6|3G{FhzlxZ0 zEg|8MyyEy5B~~2D8*&XF3p2OZ2M@O3mY~#n=II0G74sv>*RaBn0ye^ybtiIir#2^M zcR1VxML7WkYGy=Sw@ftZ$&aJCzGfIKG!=N^GUK zbF!$s4+3JXN(w!9dp3k#t0I+PsOk%QLQZ5 zq?K}AH$Sr-LcGwYjI_6{O~o+TFqIvgdLUpP==@f`r=dS)d0l4!>S0L}e#`xsqjQ|d zdJ`%vgoGfqlNcC2hM$oi@Zdr?3-fcc^OZ#K_jyf|Tr@mcgZ72Swpn>a6;-jqh%~y2 z8JTf2UvgLJ<7w4$sz}~ylIRNO=CaP_u%k0J#$Nj7(v@2cT)+M%PPX$o)$Plg%lu_5 zQIA7o@5*LPF-Q5y`O@~44QA8!QZ4!Qm&HpPT6pZ1Sf9ecn1uzxRQ^@8ro&SWBzB9- zl0}-}>@t)emQbhzG)qDun7UElvOe{$;I811Rm3{KbSD3ED7BTKGt;0?ltjf#>QD#lII(_E4tR?Ty^f1$k5Zf9exAtkW*u;>SQ zaRzKj?qiQI7EEY_SKA;;3i|<8>*uJ?`W>DXzpUi}h%pK*r2t$pJ{iPR zhy60?mSR8f0EKcn3Fl1bT_5#xN;G>0S5y+-2$|mxTp(9PY*XNZIr{Dg5J)vYLA@#k z=Gu@tD&E|D60X;ox+>5RbU2s|5`Io37;+gfGxI%AXv_7J2$Uy{FmN~jAoqNbS0Y<; zw`=L-5v6TlhOU^^*u^hdQ^d)`2d^!YSstpPlP=;f^WPqKv9U@oa=84ufyB_QK8J_D zHKfJ=NFth4i4@>p77mTGa_TPpVW((T1C^3;|6%qzZtZC*^{`m%lEsJs{^)9 zHmGef0Iv+@r=q}`lMpOT)&NU-AB2m1-;dW_T^>u%;)k6_^5#yj1(~kkho}=o%KkiK?a@UYm$cpB7)c~9#jw!MqbND^B zREg&v@5hB-PyN@WzLALp0zHPMNT}xA91l#>dR&*E*6ZKaFDs`d;Vi_vD)4HIn z0Ng9<$N;UK(4*5iK=tDvB(y)Zv@h^qbl{FdL!&JK6dc~6-+H?81LP;Chgtflak4`h z1Ez<;UZbBa+oKfOpAzo;EYEtTsu&MLUjpW3#zHnpC*VJ~9-Axd^@wkr7O2NeDyOu@ zcUZB}0{uay4EwsUg2Hw#!G-g8Hb6Y0BB)<&JaJhX@RNY98iVsPI> zM3U0tY9brkK-#U$J~*Gtp8Di!2;HNo)Ei5ZFs&85mtWZFUY4szz`51k8v5A~9nZFC zGG`>sW(W@6elgZOVW~(TyrsiU>%^0JLb!H@DbaT+BXVVb5Z`2%vO|6o76_%X;Obmc zu%Z_`j%S{ABzG5Zvp1USFpG0kfVuYf6~-LOj7)&dTsi{{w>)Z5rWA0^XNP-eN3iYN zbx%;KN77R2j9cj6NKAl9+M`R#w;P&@7Rs=wu!3xPu@$E0H)IyhsXF)g0d@!R!(I)9I16K`1V^b{N)|)K1$w+os95A{A^J|7 z^8(gOA(RJ*GIg91q)7w4rmDf8QQ&$i@WF(oROu*=<=(`7rgLkGiC7;*T!cWFz0uBO zZ>q(?#F8+2BfFd5ioMI|wFA-jxg%{7mdEl15d6;3>sm)OJD3PSlyB9Nyk(7CIEz{t zlO^oLCjOx?I991+$odz(bt4M$M5>#10^PH>S5pPd(=F+%>GB+jiif_Xh@Cv%#neaIA13CFu?LqdSgZ>(KPZ#?wB3oRe_}99U29EQ zpWN#D!O}*$4#T%K<=T~xAq$+dM^05Zm!WU-r*0Lh2n63cK1g17D1QjRmGf3JRCmrW zz*|DZm;eX)AhEWNMueB{6ykT<)xjY?j=G0njxk%q>iBe3hn0A2ftC_WdqD*$DJLf| zyatDza8q3wT&PETlIkk{aDO)fg%?rgaiuUPT~B6#R>;VK>L2Eg1>wa2gf_Fn@mbdj zR0wdy+8Vwi74=7T6=-%-S!8st83Xsbdq{SxlPPD-5aXX`Hty~40)O9Q^k^yvM|n!u zfBsUPpH}J%4(hl-j~40}v3a!X{cu;*q4$7nDj99+@v2$jqGFBM2`ObU4zF`{4GSr2 z0RV)q8d%w$4uLU0jkZ#apto)qo*W^W+!csn_#;^MsQ7KI@gB#j@5wE(hI z4>CJZTS0lB+)DqDNi8meKgG|vLyK0mS#sV& zpIX>Bu$Vonxa{Ks5g_BK4`YHkd8?35fh>Vjn0qXS>(IvK7N3|HXO;LYPWeDGGwI@s zd|h2O9`?aXbk^m;3Juc})|@wsynsblZID{JmhrKu97I}xMTcr@_alhxuKeq8!V}G3~_a(kdh_? z1mm@?7pWG+KGZ7-yM%8`K9I7Hb{nMgq0q?8aH&`1)AAp=*!4HeQdrT%=`4c0)l8%j zUt)8z5HGEJlAy^?$IK}2&uwN^RGg`B3Ml$$B@t07_w%}alYep3ik(B%0&|0<1VFjQ zPb5dM5G>q!i{kWf%r3FB?7R6*se1y!?JY5IfJPRX?Jag#M4d$$Cgv=Jo|z_eHI)t> z+qsK8_dVCk;hEAv=`bp)z9Ox4L;}gAGBIQ2U&^&(kJZj}_Z|0V7jGa8YC$=OuZyGU z$`;Ds?Xp%y!?KyaAbz4GgiXjC1_%1fjuN4LgEu_*?OZ|Wogb3X;~uxEnHPwW5Qs3B zm=M4AkwFCHFoO`EMOyJ#V*+FsolB|rd3ap#9VjpIyuV{HCrXRzg*FD-8Pkj1sf_3j zA6fKoB}+Mv+dfBRG~EZ4of4o#Ww1Y7GVEMF>N-qA(v=p{N3y4gIsvfAE&mg=kmXAR z3Q-G5eRH5ZNa0#Zs0e80p@&}L&Il z3_xWbjx{g_P2bB)vBe;k@s$GQrYV&w)go$2IX-k!2{jDj++=SF$yk` z%M(Tl(?;mb=r+#WZEi8E=!32)1(ncCeLuN?PfDv$I5k=HJy76ipY@1CO&XiX_EOGuO`&Bi&gD9z1aT z$dbrqf@+A`DD(LNgJgxSf@M48>VA14z7XGt|9ve=i7%7jq0k>zJ1QY)q`|g0O?w@+ zuET8?J`WYHIjth^8LQ3LJ>o*93h%SXOO+D-bG<)0e~X3FJAeDW!R50mMpV<_ps#59 z^xI-dE*ZW5S=A#fg!@bzYM#C|d%2$DHFgO?&C8S4FxMPtzKQWueeg{7fPCvn+ge@p z*F25Mf4K`4NwPv9_Wch=Nh2Pw|G+6ftiNoNo$G$w1$kknp93!4pMTy$B03Vb^52oi zv;tXg6!o^^3+G_hj8u!D50uy2#3T7EDV>(Y#^phpokuq;mf6XR*IJiJ+kIo-_yBPB zX7{eQw6q_bPcmGK^Fns#!ze;*y-a$Lpod>#{jxy2Ux>8y2k7MuP5mG$;D%J>HI;0j zd7Q?BHcu^m01 z?kxe>+;{ZG9juSwe-QN{B_`dOhAEpegmr#$YBy&CgcgAv$?7bC%g)ZkA#GDvITw%6 zVTHvdvEtSR4>%nwzRjCm#N52M9L{!kuu9h!KDW?mgz-1!g1{sNI(N!1+JDdmvw`ZL z>{#s1J5bhfu*Zg`}xD4`?ICu)gLQ z!ZLDNnZ=M3ORqK)X>an(t@7hfPvKC#pneEoG?)J19f3;NS_m`&Qme|mk*DQGyGqpB z$Bw@{5d7V<@{hGjc;1OY8q?&Ds*6Vs?G?)jKOona#yUL$?H!2nph9?VKFM<7_a45S zu0Ol$hA4iGB#R)&^j(C2T%v$URLW_k*jFt*G^iw$3q@_p5H zZ31ox*4zNBT`A|%MkN-&6X+%M8XDER!e0Cz-7xTAL0Z)UhZbRcb!DxE{L^u@^*(}L znYztf!5U?%=z)^4EJDy!YRy2>eiVCd!CnV}V`RqC4Q5Cc&A3A%hw8)bnNoSu5}5=X z!#xH&4>Z?@rK*Ku;MB!ar@}Jxn2ckVI%pQE+AX8gZkgOg(qM#8aP8*9mz z2vZA{6}CA0Nq{DI(#!I;#76X~h(u%QZqK za_0I~LdJo~>`o@)lC_wxR>@O{4mFh{GNhb(xm=>yh-ylfSMyqWc^dGN8*|-oFsd|7 zlg6<5?X(&BWpmgW`EpXhOPetvQoyxNYDa&}cpy){UfaJblCl|v&?Ys$M=bPT*e1Zf zqIIfJH>MJ%bAt-m}msJdalDuZOTBWyaYTA>`73GQl z9L_0839447qsuxw%CVTOY+Dq=rx2!>YZ2p&d{7p{Np(qJV? z{WuQYL!c0^*q$6=Wjp@z{)Hi*vfNu4_+Of4FA!>Bw3PiGO1JsKE-e3OO(?6i%|IzK z%sW~1Mmz#SSSIm6GYbYnV|$${DNRoSQ-u6T45^3aS6<{vqJ$0AQmIz zBMIf1U|BGyVT$E998}<31xoJjyV26z0f|Y zETYqa;loC*O}?no{XHP#4BR|5qTVEDGGsLT0>=30!eJEwr{n@b?DYG6)fJkXg>qL? zs&rlbzOD`T;*{VI~(Tw+IeB^qzG`Wmo<@3M|Ai@Z2WMTA8gvX^> zuP0fu&DQiX(s8Tvxup-ti@Y_u2}dmcx3Aex?1gQ-zKF*+ls0<1VUZe?M!#)+?JdOr zUK;u1<1S^e(T20PXSHvHMqJoAaPoiLjg$%&O|U_2y4RH2$z@5 z6^+GL`PvAlCrM`-Ug8>MtnA-^jzeVa5x;~%+ds!{3vX`QaA9Td753&jn6mdEL!XpG zTL^2~{yo5BtSV*(N7%@d*qRT@`0SAsM;RMyXkt9`_e1}FW2{lD3e)|i-h@>)PnU5c zd;>Q>%*ipTY}1)!|M`~V3A{|(5S=LWyIkTVL%OQ~xMehsoe0l4$yfMQwZ4()FTA<0HFgtJ({8mBrdqAto`?Ztu|rTsntLlg@EZN^ z)79CKb&vfTC+S)d`utieDRQp4%n~*A1IkMkHq$$*0fN~| z^Hl60sUWoCv=nyYCkiOg5>D67G_9_};U~tW7ZK7Ml}bbmNN>8QzTidQ&h`5BaK5@2 zhTY2%+>P94KUgZUuY-_-Y7n6yvktbEgTC97P7S5xhm-C~yx>~saDiHQ@zha@PP7~S|+ zg#)INTYsH@iGuAF@S>88HuZ4pc&0TptT3cI@`KoLAzx4{{(!T0nTU;su0!}Z`d>`N zyx6X3M9VJ7b|HUvOt;%}DD>;00zbaKi&tC!+C;df3|AGQ#CE@t)oauqEPI9avZ7^)teg!YZEnynMy)>nyx?d_A1p(EB zM#f7W208)`H$?5n(XAE>_QDcAgI-%Duu>b2WT*|U`Oa9;Pn8lJwN*!HiJoP>fp1)d zaiLX#0+(nobhqBQzP~m>zWHh_5M0aw8vl0mO)W^5m0se6;@gb0|HWvkR%{CL8)D#7Jk(&^K$M? z0Y^&ieAZx6w&*74j7g=kyEF{trAZA%H8<}>4Q|X1Ty2_HU`c{gEg+{MQ){(D1`@o4 z7nFw(KsjFW7!!h!G@Bl`5Yjw{m?HA7Yz#qJD@w`ju5LHH9&CP$*P6tCj{3gc;Pa~^ zcc)(#cd$@VwL!LJXHvTSc6ohNxOM!)*q*5LH5WumI~E)G5FNyOsXL>CPdSW_04Nv&Srb$h(SL#e9xI9uwzir) zvbkQMjO<%Jzhvu7_p~CaB>C(KE53s{WVqG8EY1@<-~~M zq^cyPI!#iA9-|8DSi`)ONbn+w=(h=%9F0#Lu$%!dpF&;m3{jW*KHlPQ@do20znwAD zV_7!D=44Nd2y&F5CRUSEPS-LX{6MZ=IggKM>LNrFc2Q_FUHv%TQ~q9$I61(LQd|{1 z=%pI6!Om=ZHGhw}S|88~xqO&n@z=pFV-ZbVj5agmNRfIoWKR_q8NoaMK$p!Bmd84M zf`+g$Lc+*ZBy}wv<91B5d~w!bAy2;u*F$=wzI0) z9rfwin%XVR_5tb zX}PyPZeV!UynGKal$93+N~GnKzrSoBgsCRfh@Lp`F#)FKgZ~$nl@hi+TzlzrXSnf; zSY`HW)g^)db?xxx|?vn5d?S|b1)tZ<5vPScm)u) zp7I~!Kvl0Y_At;4S`hXsR6S+zt@w$a3z_c9E$4Zh*5R6q8E20MKkGRcDW8>B+S=pq zU%_5mm})ryu0{7lyV~C2HQN0aL6BHz-1*@QTam4xrm7@24aLn z8L2-1A0$ixJL(+vw56qNC7H_tktW4%!2tEoL55e0Jcp9V)$ll(l+Hx;L&*;^eu96j zwX8!DPNkB|&3zT1$oDPyef$=ml-$9^GqK^gPpOPmv;*HBy#?^_j6E=`0qd4m&*GOiW70I$u;^oL*?>6jn2v+F4!4Q(R5ZMus@ z^Y>J{q;Io>b!BkB9R(J>7UnYCe07fkEV0pse_4(?f{Z4y8z>f>1Jz4uK>Tzs6}mDt z8eqHdA&~X~^f0aUK}{>(W}ana#INr<@*0B6=C)B(3-@5dT?Je_0w^ zluYn}98&1;x>EbP)4y{LTjqiJf7`8*HcXD;%Hq@B=-gq4YjSph<7Ikk#kzhpv2iw-1FIYEa`00 zmoBAYqdtPOyCzSKwGpQu)^IEHOYjDR4MTNmeXQ#K*+pWN0 z2LqM#Tz2vxC<^BQ zU-*yLlvbO>AIJP!AM7YGZo1;pA#N5E_i!ed&2C&yx^T|<&;%wZR zjAW*^`AEV#K8ng`bldqOoZV11s9vC4^BW261aflYYAvF>hYjkhmN7MHV5;jI{8!ZQ z23HTEnxd_gsfSV01d(_70dcY3=af=bTu3Uy48q4<(Rj-aay?T3#&F&r#a!0*PnfBY zT#9%CpS=3Vy;w5y3qU9(PG6BhG0cf^l;ve+$h$A<5P;vzUzK8-46udxPeb-|OWu`j ziT9zf2?=qOJF}x_1d0YjSP;$-z=u=uIqnt;|FV^1!u_gRs9sHQM&mR^tx`Z!IqS($ zsPNi6Ol$)jE7BxPToWjOVlh9y$y3fbC1z=Ou8Q|#4G*a0Cx*$Mb`c+_N2bh^)0N=mBJ}1|=qsd$6&%~0 z1WN$`OP=+>qQTDZQ0*?nq8Ve>h*Vt~(WIgbD`yJp%0OlzC|H0h5tgH21B0?fgbs5^}f|AU`FRO zyWepQ6I@>VW)!g573TrK|7fB=&zoZ=X>#$<6(vE8;v84WgL;1he^MV&=3NvB#=VJ~u zlMyZtHAu&D)eArBn+m=!VxR+<+Cu3EVEVB`-_(yC=>OSac&duq zno2D0e}Zz_FQR}=u2>KJ(mQ`XYdyAMf=m04CfcdClr;DA{9WuHNrC?)fz3-L|Fa*s zgrNaIUOQC~pxIBqA0y2C*kvHf5;Egj6+dHs9V9LNF_Iq@e+c78{;a;w3O}Qf9>4_bC(UFjrdj8%6xRA6zFqf~0WhHa_xcn*05j0(pAd1Uu}%<}i@6@MrAvT50yy{P(~A@yE~L|4{|52kbu!G5=n4 z5GVFz?m&vpdAenAeEK4Y>fws*XAm3s>yh-c3pDE7OapM!Fw)vnz|2jVAn-9Q01%jiEy9#w zNHL|E_#z3-NVZLit@B`bwxWiDnE61PxunXO@?`@~d0BQ6{)~edChT~?eSy6Ru;AaE z&HpSe|9=b9FCChxhGhP7CLRo;AblGDzeWZ&5A#pKuw!ON!jyx(tb`Y2;+CLP3>G$o z;!pU!`S2RYt)FFV8^O)zXX1C98QJS^el;=RXHHv3ua^-hnK}i!_)muURmZ5m`G0Dk zO$3jShw+ovCrH{1NwwQOSo0ZPfX!&J$)iVM@1klD!2ZXE`KOoJQ_0-Vod0LKHzW2i zbNwCmv%O+|wk+TPv&-WDTvFSw0~ru*UOZL-LUK~C{`(UZQ4cwdv5A4X)uK*-@>2{@ zSWx<;LfYPMACdO80z89s2VvLE&bHNUR2JBTHoMWT!xYW9*O3*)kM_DYy@kU5?>bi% z*YkuSwXyGaZ#XmoK;2u)e+q@ct?`~&{<=1uqF)@H>9xMuM=-DI_18Iym^)2I2;{onMXwv6a6e?AO%N zN`%}gpg-6MIL2^`l;(qhaYsn(=_a{B4gWh=t(V)U*@QuJgK8~uL3EGG(k(%$0-1Ke zI*Y?)mpMNGw`I~)2i%HL9oq%miMt@whaJcP<_L3)K6T5%X60xAS_^ahqWPpJrs8I{ zVm{p-$U%GKrWezX-D~SU)G~EDgwy=L`nn3JII?Yv1PKno36fw5t^q>h?$&s4f}W_n=MCpp67~2=4A0`M=sCuqu~@0P1Hqp4sGoVE9HZYgQZfU#|4zP)BP5a%aq6JAp=%M#(ioCh=5q}cNU(~K> ziVM|{oZ0jl?ax5qs%ZPS4t;nIz2*$6KUL~N@ZXse%D7oi=>3xov|nRl$+D*k9cjxp zS2e+oe9%~g;s|<$FWkS02b9PD&=V+ol?y@H!UxKvFewt5#{Oi8a-_MO!~E|gw&>2i zpi+p53#Dh@u^GAlC|aAvsB-MkVPa1)%ME?^^icmPYKbQs+9iwuag#aBAk3fu74|3o zI<;YL=%-$x*NxvdkxYEhG!vt?ZZ=)je@}#bE11hFiSrxy07N6+|_^L<pY|JNozfSPD2x@bBxLo zn|9r6Lo|2RT1_Iw2WF##EJ*aR#3GVTa zr!au637U!TV`{?QTNZoi`#uwngW!v^5!e4a>rg160PJxi4vlpdIPStv#}}Zh~IVPL)!c&if+=5T?7xN^+!X{T_ZYIY+tPp)o#S7zr zz+5SXhj*Xe!QzD&V^A?~(k$`>NYI{g39UaRe3|p5M1bUjG0j`%%Qb`p zrH10<>8q^-8HeMVi(ZTG%(Y}-89vH* z85}eIy4y)q*!1qda(cJ|>#e&l)1K084)#wofN4RJm`#lz@5c}_o{@FW%HFU>8%l*L z*(n<;g>e2d$_M~=fGg#~d8$XZ>DdC=?)KGeMSp$z+xnOtV0rz)G%!pL_qchrcXxvOGK85R)1U_kx8;79#a5&vavG3l%@n;3+LpR=u>CUawE)qQP|}oQMx$W$i_k?V-y%?ZKnuk1J~R zv6%%NUi+3Zp!9ROG6{D9Y4{s>OV!kkd#$ua7#GkbPD-)-snz=;CZJFLEDnhBgucS&%*3l5>Z4p)5fHQa0H(KE;U zxP7Y42OZ}T3p-+esfl7?OdycRJFdP^8mvm#-=X3tg<;LFMqJgQLOs&gT7FvGxQ549 zV(wq~{&#j_{z38BL}Z82)>gl8AYOE?29AHolE@%{BC%$5cB8T`4T;UXZ4{snKa?qH zk&JX#@4^ARdsfcJ>PDz`cbBu?b$#mN2a3aa{t)tO z-+o&47HkI(yJ`(*^W-~5FZnmG9b-Q#|Jt@#D$`*es2{#a;aB9rE&(@0z3~0+If?V2 z-SFsgmgN+8j%16RKl3U|U9{pcHD?eZL;(OY#D2r;EV!-H1{1G2kuId-Qe-N*fu7#5D_Gqf{( zW=r|y^PD@cVNOs_5u_ipCzx<#@$swOR{0pF6g)`z31x$T4#doTBx|oprEJy{0y|qi zY+-Kh@OsdUh-}U6OvyqfW+zC*n&_hBA&2}=2ijBZVcNwl8#(K?{)WpO?ESxCiwjFUf!sn0^ zfKfIHKmGkcTw?pHgcL)yNd%=Az*94!c1=#^wS!w-+wH{IH%?q2zaSDPFv(9!CkT8w zO7^TKwOMY{W`?l3ky^E!txqv_)bvFPBA~i3C|Y3|KX20;;@nwRbAOKlq{_#D_Sz4ANC(;94KfmD%c_@=B>mv)%SZRmf19DZF35U3ekoAy%dw$C2nD8k!sOF{{}5Y~IPU#gAPu_Z{SM zb`gn|FGUF<;sq?wCAaKnuMidASff>iV!JX@?XIv=Zqa}N1b(u_5+{Ey(}BOu+Rcm& z!C!*+_Jgc#j53>6!g+E4&*r=uJp!u8sLw#Rma_71uh1KF=0_A z)-c4{ui&$-hItCs!eL6!q~8x^Bl6Y@Mi+>xjl22_PV7*O8PN_9taJigz6}BRTQ$bF z?pT&B<~AVh1T~W@^AFAp@t*UUA1F#$dByC-tj^8}^GG<6E4a7gYv*(i#FN=$94F&q z;h4>{PgU)?_*XX604XR6rD}kf;+PGaBkk##%ee2BVXu>=CzI%sh0D2Njk0)i)_QUC zs$5M9>~fWzUqS}hn1@kzNAi`WXe*R^!iQDq{2%y+f@VkwJ_sFgAJ}y41M|@a`5)i|hQrrCo9b)v^7!=4B^V9`uefjK7adMy zJ2BituzF#1UkhWcYZ*wvDqmDsw@C#n6JOaI3dKA(^q{eh%o!D99Stn#q1_m2Hh{r+ z_k-qBMWCJ#D78pMJwhcj646)OZvWwV1%>lVJqsRis44X-IKA+gbRyhnG|3SmlKg-e z&KxUO)uT<2n1c|}zAM(+oZiem%Tu?AZ{&FfSkB+4``Rof6*DaVO`c-8RKulU=PEGD zz*^MB(o|17sf;OLXcP(1Ot1EuC8Z;VE$7432yAJvLM=xgbZTPIsiICMgO-)iN5Nk( zm>9#g8vN)WyCWOUMd${h+no<*i5K^W2GP@dfjg^s$OyAI*v%^ZoeuuUs+1*e8|Ru! zcZF=D4eiK0$$sDWIB{1#V)Fx5x~mL3^TD~2JR`z|NyVpR`)V|p#jzFc$n11FzR0*5 zZxhJ4%kMbk#7ag{-u={1QlNQjK|`!S82ZKtn|4Ed{Wgqa#vk$lzs_lSb-sDUnv#+ht^@4gik@mWRI$%~HzJe^+% zl4R+{3$qjJ{c4f0!cY*Zs1`z2LZ&D9ZdsU6*9XPvWa*Q?yphBYK#u{q{8>nI%z^kRAEBM+22kbAynbk|MoBt2u(G z>*>u!BTmKXkMqeR6%ttO z?b;F8Omxj5HzoPy6bC{;wj%DqW$o4uIQl|<{B^cXv;N_n98v00>8_h}&wIP-Q}#u+ zeOP`qog5rInvyUK4L*;Y4b%PPW1-E08%hR&aElkJ6RTaTC*qe&M>da^l_yYw}iWTlMxY_K`nXo$bK{q$0vlB6(muOYp6S52djS2}9uII%F+N z*7U#p>dZGTh7sbF(VM+o7sh7CqTDz|8TnGE^yCyNM}MS~mYCHpQ}mEh4$!1CU{SnL^|B z#t?0lneFuuy*z*fE2GkqgOyoebhEB(W;Lm?JOF%Mdm;A&)0+$vvr#*4oYSe|v@d+C~e`Q2D)(Jq@b&=TY5lM$8I zyrEE{h!lZGQ@&t;pu`nmYS^Ymv~Iu8H7*L)lkex@VZA>DaC-?Vz!rnQH(Go%hLda? z;`=2e_yb+Ftf24udllR2EclV{z-65}S?Uyql%g1gUl8`FiZ&mnuL_;AU&KUCvXQRa z8M<6{nInRj&eIDO;Q2hNH95Td&`jXwjw}K+ImCMjLP)uKS;DeQE_MfO;5BOnoKicd zf0ZlZm&e+&X>x?%@_Vl$#^sxg>hDtFt%)$k$hWiX223~Mz#z4{eeL4$#D8lh7m-}{ zUI+i+=+{MJ>VnB}k%YZu9CnAXtdxfy=lkmcLwEx((Y(xQ%Q&oXI!eyM2!Q2S^!m`1 zm;bERkuyhLCJ_wbb~Dc_L;anXJ*rn_wrpe32@rU|P=Ae2bntn27z!sP-Tul8>ib?- z&zSa`1wRJiG3FAhiL`1~l(EXO$+r1l@FNdFdX-bo)1__8s7-^nPJ!40&0ZL{q7~Sv z!|D8}j=9gfp+B+2mkR;uBr$EtwojgSPUk*yS1iJt9WMvA&zZt6&qbjVE zDlZr7VQ%+>BiVT5D6)@g4Ob4XU(li*t=f1fa2#)ZMHkNGMZ+^do8S5>#Y?=O@5I}A zQZ01+PeD)Ok>#9r$>ST;ABsI}>lA^=xhTo8sUx&To09dNH7j6V%aE1y+|6iIX9i`A zjL^Vf`B#MWhcPlIa!f7)@Vqw|eB@>2rSBqY-5R2l-IBU?Qh1DdG>=z#H@K-7pitL1 z=k5K7fjWnjr}#;_vrP{~{?0|;(yMF~|Lggb-kVY#HxjvOQJ@#{mBUyNUJ?T=r;+y@ znO4rr<3+nvq34;7r%^PTa4r2*YS~PIPZU($hXcay&$=A)V`w!FH8}S%m?5$uG7+`-rlWf)neHj0vfX;+)#e*wHYj zbfALDKqJ$|YgSiX`zuA zE4X-pld>$Z9GkZxq4|#0&5KS2CgUrR)kD`A(RtLkF71dSk<^xE7&e*!R;-B8A|8iV zttE0dbIM&P5#O?{Exn{W)@LzddHkC%-N-YHn+A>C3SFxrRqG09QIqCzhLNiVj^Kx8 z&+D7uLrcoZ+aPSq`h7#I;BTL)w3J(EFgor_eF@?%b6P>gv z0_}_3}y)E#%B+eMQ8SGqX-S)P`|$3dCoSL6bR@8&c4E6FkDf2rmoxcM-+{@U2P z-`v|eSjhg>^Y|Z&H=*~dC0#(zjKIOb;6b0|#DGGKQu5-=)&@4_CLis8*ZM6}7P9DM z!D=SBMapf;-&7Ob|9R?n4y)~YfSmXFxI*OaS1&B>!K)ZS8_EMS6Q>a;=cw%WE4}qT01rZ|u$!{5u}f}}jf zO1pc`>GQBxdKU5_pz`I%46s9JUh_Eh6})|WP146ZKe}AWx2a2v^!DGK!^xsl?x>G_ zBg+&`iOD8bDACNm+@N}j;<{9$+W#V?H+w3e{C|sFT`6u}Cd0zOh(pVF{VuQk(ZS$% zaV}+<=u{T0R`p%)uO3{Ru_A`B?z5L_oMU4$pf7x{)Y`=_6{{ax^(p$r-M2@VJff3N z5}3iQl-}(~YgS@4RQb=!?B-_bbJ|jl_9dkR&Fl8l0>fHGvY0QXne&@IM8IXI6T5%y zbrX~_VJGR*D%svH|HPI=QMUgI8Q6iY?;Pr498TEsI(+i6gy1k&tu#y*8>#(<<(A2| zbwkT`TYrb`n4yn*;kMba=i%FUp}BOyYn1Booi3x#an?h@czCsog?eLqY?mW`2+X55 zx4}w_2+r-9wqgKYs<+?#O}Tspc#WW*+o4SFi^yA~w}e>i!|_Sv^dLx=F72@}jdm8< zvY-kvazE)5E1qOLUfp!TF<|g6xk}Wd-|M^jQE!S!@)u;sj7=UHRJA-gCtO!1jGZIK zRZ83E*Rr;_=B)IWyD6}4hfDRcEu|R3>DEyS#}XA}Ta3e=lUp>*V_@38md3D46{EA~ zxzEjTLwU*b63Y`iKfmSDD%%dN2<&rhF$zvQg{Ef1mg%*$9rj>9ApNK5qp)`iUO|L` zp@W_!gibKf%+cD=#=zXlp4s8AU8YYqrg@*ux#vUidmfd>#OusTRO7O1MHE|P>#8E> z0)hPrifD`#T9!drLX0gyEhA_S8e4feaip^0+k8Up0JgdpuQ)N;T3~y5~24Oz0Ifh?+8isUxqaLRs}H z;F5*S+MQl=ccv00!m(H*MuefAf5b1^dypZAfdOYR$bwe9KMY<#db8ze?Ewg;24eoF zL|CVI^(BG< zySEGd`{2W+?ngLtbP@Q#MZ-N7GXO0C%Px-TjyEdWw$HKoW5MLY=9?Xz`Cdt%507_W zY0?AoWjpL(IgXdhBiht2K=HEij=U#aps86cc(k54T_Y`y%aYL0F>6_PvCpM-)&X@7 z-}qNMC^rRv^4bXrsfmn{d#QQ+T!dKCNevRQ@{=(IwK#*$r957esLH5|#%p^Hwwdo(&1cq8 zlwJEA3Mw&MWU~2-*rcgqC8-a4g)Sd%_dAmo&}Bkbii>F&YaJU#5Qq``xuTqg1_plR zVfYHd)fR2P8iLUTg+unp3iJsO!^>#7e{7mbsH=;*yYm_Z_2t==57tV`9L%UJkO1qa zO~lc6OWO61J-DPk`|!+YWPyHM2@bcfXnr_UX!2u3so6VDu5KkR0HcXigr0>L{D?=F z&WWfXZ@qXy$U1*g8?&QM7kG1%%rU2W{fcjkO`cBm1VVFX@3h~p>mOYS8g6g>v==kH zg+y3+IkrR1$e3X%O@m3d$!(kN`VFEfLhU3XBEp=5(Q$u2fXB9u7Atw-E7~h-@32r9 zi>)mIIWH)Zqvy2nY>{E-{?=A`v}3&Bd7Jq$DUg)Oeb(V5=3+&%to}4Pw4sF*Nk?bl zCU)B9d^92e6GBZ5%ExlY+EE0EGPWL0rbdtK#XbLkOhD<~Ju)(QO;UB3L56gRNoZiQxs(6-GuoP&7uOotlHlS( zCq6#D(?FFjvwMP*BWOJ@EiH{$0rtRonuG)$3%N`GBEBZkdeDeZ12GJhRMdF_y(x{r zKI%aTmJ-h9b0@boC)+hPpRfvz!%U^Xk}nCz*`g7SiEKmQBu3O7+oeYx7_7d3M_tlw zxF*|If)+oWm_iEBwVu_}*T5I!U6>0?G$L)B4o*33lUKobMJ1A4-M1me>q2||eRz!R z?c*gP&XQQZ1$!fzyP8ay^A5$^S$X%pnoY8;y|%IlwXr1#ie&@``hl(z2h;AsgRV$! zwiT`Mz<{2qEa&}f0|OQg6H^s`EzODhn;B_xINvPLq+0-jvn}#i@pfsGz>AU9h0pbQ z2C(r2i5Xko@_S%!(iY|-v0Rn6j@AZci}w3lo6#=A$B8OU0rvLxD|M+$BUBhe6|P9F zdCE2Ob3c~DtYNPX7oPDr1=_X^0u1qYH??BNU>Xv#qLrfXcc`+)15?ci&rjm9LhJ>i zo%blwtu1AknTjv2K{?Z8pxwfSr;hKC%@IvM!l@t;QQ_-`l)qGuO-Mu){L3S}-#g8JtlR0+ zfHNJp|H&XGC!XR7@w7NIHu#kwt0d95t!4Qs zQi2}!0>hfqj5pCtrHGCzM?!O|LT zv+m?8kA>>)p@?HXH=iZgW!i+Jq__!)x{~Iw`Y4emzapKLG@!(6`!*l>12FjQIDEI3 z&5P#nmG09|Vj%2QwxSP67qr{FctfVVNEQG@KQemnpR*5-*vHY!BPyZ1t9~`NjwsgP z*Q+Ex)wSTP6j$m8gui4F7LM_EnNFw*!u;Q5Bc6`_z5TU_grdxo)qh_X;%^I8=}y#@4Ru38xTEjf)Ax11`@ZKr=l%cQ^ZfqjyzlRMeh=J|`49)-KSKr(=ckjT#`ri` zY>b7&;xHsK5rZZY{jlz65|)5h55{|_5zv9yKs5ppgZCh~2jMVyl3E}!0E6}j^u}OF za7Io+0PBwoGJy4@RScrIc~76+WkKFee|FT3^4Z0R#`1@mWTvJg_Me0;^O zU65J@cZrSM-;Qo8>d-Ahg6c1Ghk6R@ADwB_^X_NC1XV1LYA)0Sy~!M7n$4<`)G^nP zN_n95sAD*3ohubvb@<+6TZ+cRv$PPt!p7z@W8uA2mnlaA;c2qutI-cflXIr4WOAzd zr-d(|ngc1+Eg$(s@$*x>o$S(K?14AV=xfvHlkl8{g@7@a)!=awCMYV(HQqT(ocgv0 zFPg&TsH&Rl(R#n3G8mDIqmJIWId%(ed6e9q7QuwTooBh(YIl3l`6T2p(x3=r?jf_L zN9yBP6d&qk$24Ia#^1|$>PyCchmFD7xwlPApkhGHKZmG2B_dzQt;%T$;1_w)-~e6T zD||`<$gVq3qY5{}&Gdy=CY9t#J;6&y^ct_u_d)5!8LK! z2t=6PNz9}-*IXe2N0Zv%AqZ|wS1IZ-ub~lj#ctwCp5RB0nObCrY&j^=qv$wpxbLNr zZoE8sxY&6kfTjYsG)9e1@{JrBj1JD160e(?Viyw6u(2J7g`u9LCqF+k^{KUTC56_% zM&>G@0^UL9B#UP2GbA9-_@T7HW2ObFv|Q0|=GO-LB@H#-#yma(zq z1!&(fs2-fm(kE?Yvl!*(Z6Ga_bM~)$`pq5h3NkRqaH{qp!;~MEG)n~sdqyJTmIq|3 z1X!o~6=f-kNGuEX?dt@s@R<*obWMVwtqgUmAKEv^~=nb$tnPGJ81R0`Xi|KgQe;xA)z>^p(Aa zQqyG|OTD}mRXFNMezkU7sdM6R)k98`6b%z=m1mxg<*Xl|<)We@QhJczpOo=@%H?|L z3?02+T(H77G5VggI2{7LOic$8@(?t|ew<+0`Mb zO8BN=_0ytL_kTYf`Kd>{qOwcRn%`@60_%&2L%Vm{M#QY!%0gK=bE0_KN`PHu!f0s> z9a|nOMtgnLvMy*_%wpU^LiN%K(_L37`*|^i)+-Ml79JmS)M{2srr-Z&`qtYDhhw=} z;_$+edDo%=emsRV86ztq+^5&RGQ9#lhWgT;fJnWd?IzsbkNw~assAud?97&F*P!@L z#*VJ|*IqcSrAVk}z3Nk0UM_c5qQcp%`UD<~wN&5HZZ7L60a}YU#~NfKYC~#oMOQ?y zaj03zDko{cxq3g=)ikxGTljND4|U?-L`al|s`a2MoL{7sIh^EY z@S@V^@-toI>z!((lIU^iG^Y}{J$4|va1cQCvlY!eq^fiu>CTQ5C(Aiz;H%|AGP<G^iAUz2m9P{=FMItC-_WhoH1CgU2}d<8R1w?mqC9(BQR=nt z7JjSkP$k6euxtdp=Mk?4G~NlZQBPw87uF8qVT32^RKDA`#r8Dx*7tS(Y61{{A@h>5 zufk{Kwm4LDo8SbUOK~3n>q6Wqi`9r$jutc8sR|~;rcf<9L3%Nt!g z6Q3Ulal(JU#7;qFY@ov<$3^Z!WUftcP|Z6Vhd*KzoT88D#l+C%n#R*Fb6dg$HUno| zrrt~Hl_sBc&2y?3o2;8Z4M{H^ah&O5agTuw53j*|by(6PQ}yj2uE4(Es^_$2jSWCa z4QUb=hm-w6Ke!IXcs&|fGBi3XzkF^_v;OY0ShKL5$tqFx|9chgq07~F4B3oiT*VJ# zf+3*~lrh=fnh+~3+NwSlr7_9>$Gr;U74)%2j7Z~i{Gpi!4>s;u`rfW!y}$lS_f9zF zG@PzpmDJjnYaa-gH&8X4Ji=$z|$XzG>fRS<2EZY2TE`PvkWm1ZM zrYRp>1Ha6cq6}NREh4`$a$|ZkV$us{w02q%X!1w+8-$72gY1d4WdwgPG={a8@`zd? zI1CN^Oeh4MY4jIxNq8dyOgbGPulr(mV7tq~z~&U$lrz9xJJ(W`GS9)uDN~fa($`{= zWLhJn5(*yA*Hwxac(~T1SrZ>VN#sS`lGC{-I9Pelxq<9<@r%y6@A9d@T9CDW=G=+< z%c-S0y}_}UVVq!*Q;53!>_*<1(ruQ%D-g(}`opI&-U0AWM|cqZIsW#CaLWVKzk9x) z2nWXYrvv;I_iGRQi3?|R#{cSwzXE@y%1>bG;eR2{uPJ^lv7ae&85PIy!9NNPZpp%W QaKgfv(u{)S`(d8{0^$hQa{vGU literal 0 HcmV?d00001 diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.odt b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.odt new file mode 100644 index 0000000000000000000000000000000000000000..3c996a1f46c41bfbc1b8e7da638a57458072b45e GIT binary patch literal 5653 zcmZ{o1yEdDwuT!IPH@-Y!D%D}2=3arTVqYrxDyC{(I6KH5+F1oL4pK#5AG1$tpkli zAOwOv?##Tp^D^^x)vj&zuc~v_Uc3JKbTu$A@c{p9EC8!MVFJ#F#b5o=Lx;LR!9IZ= zU|SClHy3+b9~YRrfS)y?N zNgCJ}+c;j3fO_gChYYev)g&!2z^t($C8>CMZIA)iswZh)Uo;Lc2(NAe$`Ptq$x?#} z{v|AHcmu;{w=1&^<&<%10*OXVCh)k&YPz=aIPp?YIxm96KqA`AEhDto5m(wx__4Qk zfj!tJC9=2VIE;2KQJA?^Z4jWg?rgX!Do4P-z+#YX%~MyT-g$tfFHvui0d9I(S){KE zp5~&Mn6XnDH07e1t!|{WbMi$$9&T#wzeS$UW~AT<`tCzEpwX`v-!9aa6Ze)fRt4f; zGZ!?)&HTL0x8`hO%oqkL&FHTD`e=CXF**D_pi#*LDyDPJ0N*{k^Xn}g(CDv?8Ex@a zX#u@|BYlB)8q7cDkouL}2?Q2HMWDC7bx8X9RFabkM{&Zks)*w}9tJ!r8PN})4?ALa zG054e$n)T-2g={Opz%RYgm~%LUwJd+)5aOnS4sL)GXNThs0HqlwKs+jiK3lh%@|3c z3ZQd5;9}^Cx|Cc$*Qc?aH0hrZc|_PlzV7ca0RK0mW5SUF3lAp_=l}r4!>FZXAjhYs ztt*KR?AsAG5={8akFkFHP~794Si_D8uJwz*icepUlQM74zh zo8g>eaPwOBL-(mg^|OtUPbqWb+iJWYb?x(sE7sPmqN&PF!da2(s2_1j1}|TVESK+ttHZ3S{h>t7+2?u zd;5(xnt}t1%}llFAi(Zg6=&SGdFRatFv4l_;-XkrtvP15J?sy6#TMy%_)xVYGFU^oleWjWa&4^wK=gr9YKbBo~zkAfoB`vH>UlOR3=67pPkzW zxzPCH4Vpip8oK+Dxunq+8mlyf6w>ey#6OnA3nUrS02~R_!4sM1;W?N6we}*0lasEF zji5xARk%G@Ce4cqQonbjcDppVI=2m2_6ZyGOuN(WQOK z0f{a{$2)wktgqgsYm4(E*ejeYg_yI}BF{Q5i6NyQJc4w78W=*`82)3Q~t-4+eD3_EaQD;%CIY4*r;^zYRgn1gOducj1If< zsy?fveA{UK(i7ekRw?AqvH_FyP;1B2XF_7AzkJF|C>+Du3LfI_I~)POg+{b>N!EKD5b`Osi(q+VvnEoh^H3Y$Y_kQ$T;E)1TS@LbWq$Bt(jP9q#!lm97qnou!tjf zC?(d&sX3zn`zNA#>AF|q!w^B;`X9u#1*($t)YyOtmX#TubOm2BS5%6tUmnAF#7L$W ztrEE4d$K_(S1en)$ncxQz7L+{qKfQ$!ZaZQ1B}ZmlR=IA<&UR62k}Jnz1!SuOC8mw)@ofuIo~$Js&iyEE_wmu<=jUgu#P;h3I{o;`XN?6kMSQuuI&+#N z6kF*Zwc{KoVAyj+Ti}1DN1;&|XQRU$%^{XgS8M?!Bm%OSl>>QN*wM_z*aCkMGm zY3BpJ9K}JcczJ73FSIfOW&2`|Y2X^Gsk~?C1nX)SOq|DGl-HtUA;pG5v0GFeiIpv+c#;MD}lOZqxc>{;;zX}_GeY1Ix)o&$;F zrIwq*d6~VwH)wkIELJ)4XzN7LE@?-iqgDpQ=GcWVoTApy*lkGo!pt+YtkkuN06A*s6Ua)b4mFl(G}glH1G?1%K(( zK$?zMQzA@Hkvg=iFZ7GsLiikZE(-xx4%c@?tHx18?NEnjE;`jNN!4yrPuz%T#v{_j z;1@J@843i$(_hrA7M;b#R zQEX1VAUR`8Em=8{EbCM$2=YfC7wwXhNyuDOt7~pt1#87kc(R(HiVh8PjOpPFG!_jS zUM59z)V*>(cxBlK>=N+(oDk!0pmHXz|2S2MwG5I48%cMLibg8h)8M6lqP@+Sem)+% zQf#+1(&_v5nI-XzDTm-xH5@H=N`p(0+j5=f698dz~pJLUo);0r-&&*wWl#F@fo5Efo zlIS?vBK>CUJt6J4%NbRCl~UxI!5Gg&mCvNCNUwe(!kXsI{1bdKm{c0rzBk+85$m6( zw;H5y2yHWZ?Q#GGRodQfo|Y`c-S7Z{luku z(z~$mc6c7M?6-YK0p*(^?E3Sog*UGcrf5SY^K z5L`U8YX_?nr`cpmwSem=J7o!8xHoRy_PU~0BV6k32#*iOGIovjgM=98%6U}up?yj+ z+w7bQz^z^9XWH-H;{gS*_t9Z0Np{y8!3&K12@MtBCPw|W-j;*Yhb;+J%7?~W=JW~u zfTw*VVkKNdHf6UjWf#X2njBLHLkLQ{nitB~uZ>r&qzW5C=?aaFGq#eLv+hG%iEb=v z-uExR5?3~^YoRb!@Fz1$#i)9e{X{{j@lD*y9`;o-dlxVEy)H>`lhn08 zdOK~pK4Ab;43+(dF0bNZpBKwS+WXsnX8_EPZn64e2V7B-QF&J@HoT;;)}M*OdAs;F ziZ^aKllM+VSz~P4z??8yxKHN?G^Mkpe?Ty>cm@3uODl zpiiU07U)Pk9^~-38MBb4E{Am^Da7J&Lw4j=wE7fzxY5AIcbSZIOoGmgFE|34RdI1qxRi3AHx_)}YNw=z zA-?$XHOO;z#Fmgq|Jgjvl(b#i$(Iwdd5t7?IK=k+{cuha8Dl#BxBdNjq~pqN zXa@0vK(F;`%5X+2%8XFlX_PSsbruJii!nUBSG2$&R$T+PTPrm~kF-OFE#4ASFU;x+Iob*0Q?A*bm`Fxe!R2BrzmZ>zl@&d*r-UcncyZNk3=Bfj4m*GovL9{ETV|&B>WbA`=dR3iD0jk-bkguK7X~ z&#~p^ENYIOmOJ=4Id8<-yfC3}hUrGP!SJ*Bl)i(5^0{!CJV!Jp`*RvOIEHr~IaWrW z(a9sCt=k2IljwBQ!6R^KO#*2ojysFZJewY$e%uqeZeh!s>MEQnGq*4HdxmJ%u+U1< zBn7t1FFCWEqdvlcP&XjgQ@vV2-Qj1vn1pLS>w~BRS1}bOeE&CSbZN5q{RRs-OSRnz zice{xyrVipYsH1*MX%Fth(axwCz;pdo7+kWD4NZUf~*yvRG@65@;Yk*7Pbw@l{xbX zzdasIU*()P88g6;YKwXy_NUgA>H7&^-z=@ZqM+B+)eDZl+N10b93dgy@J_aR6616S zMP4hs_M@ZsJV^`mgApurS^-=}T&HiE+2?4DjbmmR4G8xr&KIa__Le`>eJ+vcW6P{_ z^Vrc_$!gV>51@UNaGBjRx<_(vyRg)PppJ(QzL0&o%-y6{+*EyW*s%Yq>n+NLpUJE5 zE+jg_CW(aJJU^?}4G!`=$LvhovYWtz46w@YH)h}+%^G_<3`smL9P)XuD)R!06;QskYWP^OR5Y122- zxx}W4wYY~bjk9+&v}q8fdl@`y>N8=*d4n8-^yl;IgBTQ;k+c^~PdOp7 z=ybeF{$Z?4;m(lqr!{wY+c12jsJX>!{OIwZs*?Cx9xyM_Cg|l$ZIQ88l__Q#t^L}{ zsl!(d{`iiwQPa8?o^(0Vest%g3g2pitqs&7PIInWgyg1s=4X>Tx}Z9Ny>DfzPiA^Y zOgonLMkn?nPo&27sZ2L$4{gHc``+x(%goO(-M4IpjtjP{D;AeKS{5-pM!**>eHguA ztdnw-E4bSeDF3s8Z$skW^aebHTPxD_~F^o>XQ<($? zN1um@{7M?c3F~@UP`ya!kh>La6wUH}JNKh<%~iIDG0@pOYK-aUfbb|sF7m@7@vG7I zD|`6oo!VcU6U;5zIg`?kt?Xh3KCuwTsX%aj=m@u|e?3}iKE{0Y=Qno9Nmiv_Ic@F& zm)>aYyOru{CDIMHT0Ux3YXYUr8=E|r=qU~{_tSXiyVPp?)9UO4LF6>*?h_9Vvkoh= z9=2B;O2GW=9>q0|O+7}U-z&>W>hm*y;~fKEuS|o{Yew0L@QH?C!^VtJob5Q(oVoS1 z{=?J?%Iys-!?CmGHK7!ArXnhc-X=qNask)ZCuNtoKW7#edY{UAldDw-(PF%1P$Cpx zjX@G_cB$nhY5EOz!1G7njtNw7Az1V2h^d!He<0S^ou(epmgD4*pgdJ^+P(0|~zie|N2a3&|c3 d#NQrPR|D&T&H^5oEjGaHfz=i~FbV+Re*ixBRN(*s literal 0 HcmV?d00001 diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.ppt b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.ppt new file mode 100644 index 0000000000000000000000000000000000000000..bb3a3d6b41e2b2123eddf7183c1906369a89ed7c GIT binary patch literal 96256 zcmeFZby${5*Ef98EnQNVgmkxb3W6XhA=2I5A>Azv(g@NZAQA#9(%mf}UD74rdExfn zZf^Ip_i;aey!ZRPdpOSXT$9YKnOW;MYi6$gKFs;7irWjI8%_`e1NwG_1cLu592%f& zfA}##pf>;w1w7xbuC5@#RlrjRJb?Ef_kRfo&_Sy(f~b&wT!W#3bRsDTG!OJGI?O zNbds@{w-JDH9r6kA`|d}%oz}<5FNrp1mJ-NApvX>2IZhz0X{g;)uj;dB?m)+41g~P zm`B%N2n`ax&J4+Qb+vOH0-0x5S6C1l!f2p#z)(O|hy!S0=M7jqIDM&shkO~>pnZj3s5Z?~@LIIh{ zK-jSSkW8RU7!KeKsnpL;ISbz-T_6l0Yu5P68wA=lwXWQPB>3R~8Xa^7bN|}^4dy!& z0nhj1Ai)N}^W&?-`N!hWud{(bXFyd%0U8SQ4F(+)j13v~=%7$++UxjwbOaWMai zqk(M&2G}DR7@!~FfiVIC-2thVBD4V`0#pd3!UhJ2rkJ&rgQ1m!wz8{@p}iK9i=_oP z8y1=-^X3?V6#b9;e~AMg(LHi)EExVfQVo8~^r0j?VE>eZcYT=AH0b3#sqc%fmS$9Y zrWSK0L|@uej=p8zH&xy6bJmMWwm^+&P<@RNPKTqr zYsI82k~EHEretLI+Ea5j3O6t=c3|b2{?R+bZa+Dl8kWqa!VOJJJss31dz*KrMktQT z?HMv|EuTxp4#eP~x3}@WiiwV`g0G8S_gwYLQ!ap2iSI&-;2KbAi9WMvZ8+td&vj=U zF__A7@#|)uM+r1KaX9IX6H~lOP0I@Rv_^9p*z_qxiN@=r&Ez7WJ9DPZcFjQz`GU!!USl4nIX(H@4CP zC2mlScH3CGMI5upu92?PAXOuZ8QLrx1(P=>MVq$ zLn~v)Fo`|$3Co8(1q`MhJaK{3;fohrlTQ5(c4wTi%VfB*d%CM1fZ8p6%a?^fc%d=G+2ori zqnH9EPAiz+c^!%_{?3}PNe2mb;E!_P=iDrSLjOrB+{}2BslR6>2DJ7K7C(}KgQ#)<3&LEl z0S~WzAkg|XKtczdz?6DH(Ay3S0gwu!=%Jtykgib)Bpcu$$W8~eivR|`z~TsagFq`^ zzJSD)%|POI??GFhtROi37SMBfJ0K;L_o7QX(^-)Wue z|NEWxf&Knp|Ie%}9szAt{9X|d6o6r&ud5CO0N)>eFr+n*nBU^Rw;bkQex2))1p6=l z_x9)jB|~^|of%R9;B0^}$mjrA^P7Y8%a0V0Ub!9^Kpdn`u8X~=E4o!7zcpVVHC~ zVMMC6XUiY$XwX^g?uG<#yG|E*y5BX8p;P9GFuMG9ntC|PSJ5WR6{uRO%m@dM?N0Pj z)ywK>kvr{6;nkK8J|+C%4o7Ft5sO<$(z{NXs&aX`2qhh|tIw2W3F_G2Z)Vw~+)sb! z(Gq-CQ@T?flBGkVl5G8{)U_3=rZjj(ATjCz+r8M&dptZ<8c4Lf=9%S+jVPDQ4;;y9 zW}g_DREAio1rFmQ*{@i?Gu=rq>8QEC2lb}mnK5B~Uv7u(9`4I>`<0Fdm)=Ld+dQbF zI%GYgRn&Bbl#yL1vxV&aeavnZP3Hig{vF3euG5`00X*UXc!dIdf09`KQ7sVtCBEZ( z1?aCKH;kD4#^8QG`~h^eSW6;{xaf#%Z|g`r*r+xZYoqlePWjK$F(uB(McJ?-a_;&w zne$EpmPcToEOs8(`^dBJ`?)q$JhPCLZta=Cgvr_#>&Vg)=NbqMhCewGX5wp%V$Ww- z1jDTJ-J+~Dd=s~q3f1FFdoKse@RP{?Wq< zQEHpX+SZdS81Lq&xA?@p($&62M`K_Xy~dUw@s~V@lGT$^Z4a>+dM>b4wVAUOx4D@V z>(ry1`;c5|{u#zp`>B^R{v7LD7n(vx`8pc91n_aAA4BUP9sM|$NG*4mf zPaHlXHr7k5)A>}qQl_qL$tCeXP<5@8QkF}UCri!ujGWobHd?h=QllpX#Oij4{rroW zdqA%Ej*Va&Yh!^`;}TxJW;wQmsw5Y;OXFEWX5q&9t?>w%6tJO?M-S5}i5j;019qdP z)<=;Ocs0$_70N}njL9YI%38tqwDJe*+rdWb?@%W8lhK4`Qf#ET`U*(%TkP2DXFZ`+ z%L&>umyP>1d{QD}o&D*1bv!Q@v$G{?XlOgdbQoIk(2fL0!Ch!S?~x=G=RZ}%FdKFVTCU2b`a% z6*8aiKz*HO4ej%(>s2(Cd!nnF4EuHKL~7dcvI03nQ2bQ=&X^>h{ZeR^$aZbCnCfHs zLa0+y*O@J`r;pRnzRm|v+xtFRd3mSe#R1I@oZW{B@Qdnbgwnah#VKB@qo-g$KlY`ou$8v^CgjMOlc+pjH9we{hVN~iHm{bFn5*^ zsa^HEI;>?ijN znS2u=-4so~=Vkze)lY)zX2zS7{r8O6fNJ{7ssA(B>HzZkUu!ya5Grf~IRpSh?^Xj} z3=rZj1Ykjs7(4(u&_OV})FhBE>@E!u1_eUe#Q?rH9W)5}Iv+%jK?;VXKLfs4zlB3X z;0s`{j04PjPXW>!C}{`E2NaE-UJ`VzAe(_ucu+H72f_qh0dFLb5FptQK|XDe|Jw}E z(111#j)nLjC4c7tKS2KA0L*V3@B=uY3dH~500vHa$)C6VE|Bs^+YLc3 zKp*MRHSEjM&=_}*}PG$liQ zYbyh=g`tz71)8#nsof7+(%R9^!Q{6HNmHXAG=x`xiu#RLXaEHTf&egR5QK)grXlS4 z&O@c^c;NMY6agLfgNLv`jiM(&{0|=5(Ejx(`kj;Fz_@YvAIA_oP&PJ*eD4cjP%Lbl z|2MPZ?s4ebOCZf>fNg&Q#o}Xn9=vD4XooD5ztCA0Az+%;BfPqgt3{?Gm2WxDg_Nx3 zJU8ng#Pk`N)cDKQ9!25fDN`JFiJDp3IwpFIrE2ccLuA2G`41$Ba;vD3voXFTti>r^ zVQ!5m@5$ei&=V*kA1hXpnus!af)kn3qp{YGlvyK2h9J?Au@ z=~!sk1DK`%j#5}>v~>poZbbpyx&g*=qg%|{SWp9Z{2KiR8f_=V2y^7D1D?ttyhYZO zmeuc4yK6bul+U6jY0bBee70wlm5i#cTos8R-`qVI;Jf9BDM85Bqp^3iU}HMBQ7S9pqcq-n%jq zuGJ~ZVULcof?4;FAGru3Y3N~?PW))sKYcvglx@~N=92wJkp*2DwT_{WWoUYMMY#E> z^lI%nCbs9}Z=x5nuMXRb@j0C!7KDFq`atPBN_T)ML4fFkh|Oy$ZK7*q2w<^mJf@-@ z^_dI9yKHzz=k)bwFbac3^@8}SU5UTk>i9XxZ;^qvcf1lUpyX_Zucoci8C}KE9bH9* z_QA*GiWTnW)5Y`S0@aF~>e2zV-OU*v_7Ks>Xo9#G`-_#_%iXT;kmDuQcdHyOE;rlB zwjXYz=Jc%EDea~o-`zP*AMe1M=Ch=XEgh|alb1Ok=2pEILdwr;rgCM(Qnyvx4}LMA z;4+=`8ZGw3<%#B~G8~z0V1I1aix!z0Rq&GBs|OlfOP9SIThP9hgL*sn_;=-ZmGQg^ zz|B6bkG#uq9^w%nP$Z!+QL42RPOy4&`4@c^?`C&}RUmz5>=W|5Wq>l~1 z`03j#dm8)>C1nTU*l9Qd53TyMR3%LGEE|pTSD&bnUStb1E!MV#N{W20&v}Mfd4Jq0 z*f*&*n*K#GRdIRsd4EFj=<13s3UNce>=1v8+(S(#ANk?o4H)Emf-$)^`r?!W!LL_O zI)h8`Tc2viS2m5iCDwT^j0C@OdV1bmu~9t*W4N$~qiN?p&fgp?gS=?C7Xxq3u~+`^ zp?!zan2_#Q;is?YdOrw#aqikP(2#ReS5IghaJaWKBM=`pD|TAmkk0UNaZT?V^@u02 z(%XCCeeLK<1zH%635RQr1u%{z*G7BAC9x;r7+qAbW#N(1cy^; zY-DqD_P3pj_k`a7Sl2 z>jE2Lt?!jaKKv<@fJjwksi}!puuyTiO!pm^oh3D+n1PhiEZ?hPJ^53s(bmVvNYZzv zA0sc+-^yd~2elELG)p&`$DI(OJic{!x3N$kx(DkOry3&mk@n*=s5$S($+C;~Vgfhq z9dE4kidw<=Ztb!mFO}5}XZNz%YJ z5cwcR-=aY3;nbG*X^`6haz41GuYhcB6)jNesfe9Seh1dlX8_!OXTcw_?M<}?@Zl%- zH?ZwZN&kCpCP4Q857_pmocui_?GJ4GlhnM4jBj?{@3{wo&c4x=KX5QC096lxlp_!j z2e|KuF&7;Jx*stV<0fu`U?v2h4G`R9a-IG=Zh`<35GWHJ5>E#_S?CZ`CB*mLeW&07 zgcA}H@(lz!`3Yx!M>~)dK*858|Jp3I3(&(*{m{EG7;q3A9rzOhg8K;p=>X~eg@C|+ zjesEa`;H|g0mN*5je#JD`OlCvWUCv3f_$%0kjFnL=pPjHpP`^S?+jrA00pT7L+Dp1 zs90snYKjHbldRkuJEl=7m;b7ywbZ?#~L7&2b1o_-AApR(Z^czKz(n*{Xf;^-OBBSrSJTetW)l|QvR;8=Yz zGV-G$?)(vBxfkmQYt!@#Zw_T)g%b{$VT3WVxrq%{{G`}ek)-s~ zn-kC4iq&{%R#cJY0|GED@_P|zqwZvvN+Yp&Gtdt^t?6s z>>1O_T&33w#bet6vL|Untr>23W&BTPOrP6BLm}UzUh!QgURLhyyOisQibY*3is*bv zMDUVo<}C(!1EayI6tkF;p@Z0Gz5*2$S-L!E@KYAo0){D2#laMVo=1RS;~N48Q*Ffy zP~_Q*G_et>nCzUq*iEhv80OxMb6Vjph5>v1F5&P2;a9kL9N4jw2zLl<-F-i# zi3h(%IsA&mtQ%VEM6yjy&v}+whjhEFaFRo#+;J!YcRtqYxL8C4DpnSGMN*tFA}K_* zA{>@a$|k_u4&L}H$IBAH}gn|c^Z%64n zrHEHm={m>dU(J5-BPH9}6KMKiR+)Z#B4`w;)g$4VUSXPPo}|lMDYKPGfRx@#`&PvJ zx5_m#G8kyMx4}fDVQQ~nW1_J|vGre=lN1Fq1|)B&E7m&CD#{LwO}gBptlK&;Oqel| zDaj2Abn~*PJ7aYIPfd1PMk9p4R@)DI z48%hC!>VTikU!8!^qs#(BV0iG?`T8`u-=(o+wZ_2IglgB5;)ZP-In)f$b=Kf3qdCP z*T`h+A7t_mGWpMti6T-cQV=k6p8u(RX3A=j3&WGS-y3|Q;bJ*a8A}j@AE{)G&jPKV zehCWA)*HbojEy%hSZzLzj$hmq#Q9)E7Mkq?M&lIgO=icRZ4%;&CHAj)t;+jwHi8B- zbfZ#Lp7-*i?)DQUJrX6VIE|3B4F9~H6`^l7Q@s{4_5gk-W|Gz{^OTW*_nRZ#oz%pGi<*Ua||q zxy+irrPv;ZPJd37`7C!O?b|5pqJP#q?{3q{ok&faZmI`F*@jj6hAFka>2sCIrF76y zW9n?3l2Qvu2M_aIBuQaXLvkpH;TK;yK!2fKC{HpNG9cz5mZY4m5A zw<}Tk^U(5Eu_RHk8EsqZakAP2v0JDFGptBlrK(Kd-lRm9Gk+yX86-kD(oXk#A}%j7 z025+ImU-;B{nc7df(Leiqq`X91%@CT6jNKRDwU+zQhmWQ&;$j2O(uA$XQflZyk&H~ zchl27_5pn@qNdsk2V2Bxwi{>7W|Etzz4g5hwjPwyMV4JKA&hgo8D+2zjTlFhpXHQ| zHH|51nHy>@vbB`AtvB5)2kxLmwfDzL)OvbhK1KwrH9jIlvc&XyLuvcN$5AhYEmS+$ zx;umt)=s0`lzBC1DP zO5A5-_wK@aEGxfkS+Ptr$-0ojtiqYqlEVd^X|@czqYCHMiewuwhc}CT_R&px(Wu+i zCbw)2qb?Mt-CC)m`e|~GRw{mwD8~`kV5%+;2lf7k&csfAl}UVW1MoLjl@61rok7f` zc$I({$NWakSAvY`$Fp5opP*mhGUcRw8-DrB;WF1yn-(?<*{WI5X6l%_avtVDZ2k)G zw3PAlm-pH{tFjFP%o#>mooMXcePiBQYwjLbhi1*YWR-)TJ>Oj3nRNR0`zGAahkECo z;LFDV4e+Nx=qFnZLeraSw!l$CG5-aE;9~5JMJ@X=)znm`j{5C6qNB{HAp4VBa~`}& zss~FR`syP0q=hjORk@$go{X1llM0{s6j!{KMP^#keM2zQ%{p|{t+~GcCL{7p3>?zFlZJqQMjGR}wPA-u)L!IZ8$EY@d%mdcDpB&LrVkGvqz>|aBn&o`hD3+^t_ze6DiDuG-EfIuO$>+=81 zSOWn?5CaV008j+|8~r;d8vhT>HI_g#egGuMB?idu*6+6(Apgk^3P|$Xt%fTAZ6ShS zVE|MGY7ex}J_D2bC6Ak(TuykFiY)nB^uygnyNcHx@@A+Xzr{mpiA>Fv zEy`1?(3TZH{vf#>#6gWpInpPGr-$`}N{2B5 zZ>jZ}nDa-Vm9uHnBNu|1S05n{-Q66Q93hM*Uz7h3=W4I4g?A?8dD|u8|Dkbz4$e!Gp4|qV$v-MEAAw=D6i2N>oFh z-6r7I%|lN*sBdbZ;STbV4oOkWbN^yvEL$lM8Er=vojZ8O1Kq{xbl2z7=T?uS!Zg7~ zNDR+nhuGLMOiek7n!xt*X%gk<=<0eQF1g|dVu*fK?_!5HU!F>K;E6JIzM(#3%B2s< z>QQf1J|!$)eQL-#!1;0({1qh}tF0V0M7z%zOvw19nJ*(`b81xGqG2YoGSY+VX}4U< z6^_oK&?@#&x3{MEzmUt%=q+!wVC5aaFD^hb-8c>4AJqpr4G^>w0GtN!R2Xa{pmJi) zixy+f@dPcUFXb1d}W3Hb)aQc;6}4vfJE5>p&{l->m~ zw%TK4Ng0ImT(U!lc#31(I;BxBJFcqQSh_RhTfEB{mL*`VxZ`C_m-Mn;GA}AMM!z&H z8Yb<3U3WTq2{h&Vocy_==B^2Nen3M*A%g>W983%?4Viy>Kh(9-H8!;Se%at5avK;! zc)>HvIeLOy|1FJ#US(J5+ar)z+HH~c%)u8^W6O(v+`V%+BdIcI>I`WJXrYEZOu@Cg z9t;IUFDIplKQLG5aa`hq}+SF&F-g zApzZ#(zS2)bp#dPw0_xeim7#9^{UmD265oLd#4Q8#(9Yz#_IQ zNXoI#WSH^w=FA6JY$v!b*^S2|&80=9bdGbEn}!b>QM%!D)%(ik3QtLUjV$CU>sEwP zBk4)zu(1s7#nl7dzuiW;s&a3>?9p7IXUjQENfFeZ!4$&%xdt*kF2 zs=u=07=U4psj#gDp4`{X;C-9+IdSlq1`Dfc^_25+#3Lye zZ#SOxp3bysK#&wH74P%d9+XS$BS|ESv-XxB z3_EWU>$k5rf!|kd&?ej79T#=e#<2DY9->sTGm;omT}{fJPkbE)KAEcQS+lo(_tJbs z%tMz#*BZPI7Wy#Ew&+(dqL^B=h$5 zv(o(L@p-{ygBOSmR+ApEc}$LQ@AU8SaX*VfJn+F_mdoMC%WOm3_S1Ml`H4`fh zQKMkh32f9610m}fC$8G`h%4{83F67V+RN^&(#4t=ds%&TTgIUj2~k6R7McXBR>Kwd zy{2=}y$Ah_g-}G-#7k)fG0#r!zbi{QC4hgPCV2dQS$&@E*?pBSI?yQi_PK`qy-Pd6 zvUia*S_u_q9zW1ico`W`YXBV?c~GuY+d8D;*7@Z$#<;OGl6$|CE_yn0tksS<@|8iP z)$7`Jmpr!~uF>`qBIpN4_b9_VEU+FTgd7#q53?NL(0EZaGup1-;O6>8#6RzI}bSvuM^5L?149Y$#26XWTKrE~Qsp zVq3@=HBtl%MsfWK185mi^X)d6>1GFX9Hko@+oO4S0*1%}Wd5TGl5h6~@wg8;KW%-1 zPf1SpHJUZ-v*r{-OeWMgx_~1lY16OwAIDrz#Ls4(ZtkY+ldh-A+r2{>5!xoBAz-2DSNQjv1?P8kv={=!N|! z3m2+kbHv4z6Ufs7(q9s13AOeL5Jed*#-2m1h`&P<6{!7^_U^e_@~4`;TBW4WYV%p? zHG_b^(!1Cij7(>Gv`g!QN$Go7MGk(bi20-C}Lp` zwF?z{hPu! z?E0q9*ND!F?v`5bZbY+9do z0S)`rV(2C&2a^5deql7Zd7|hhNc}xG3&78x>j*_6AL-m&`RIhz|_iEfLujcl97iTI5)0qWuR+eZDlAx?rLaHE+mMC z%&&V--@@*pt_>I{z{>uft^m1-gM-aIW@dYR04eL*Gg;dhS^;rJ)^?V<4uIFrnAyNi z*BL0-(t??dg@udRQrFaq9IR)|DnM@Gz)B8wZ~-U-bAZw_W`l&WLBasaz#O1-_4R?D zegIPZM*Ii?;=hOdD-HXP5cVHwIDUk1{0QOv5yJT+gzHBL7dhC(!qmzfXc6Q?4mPs3 zkoq3*gCb`ZEC-Roe)=~H-^0lL3&_#(|M8Yzm)v_6RLF8tES}M9Q1=;z86OHMJuE{Y zdO22Y#3vcsdnw-pOpT*KXvGXdrG2kcw_4O|+QsjlBv_005W3-3?}>HIIm?eQ8ppSo zm$&G58B;cGF)^pi5XYz~xSO3O**C<+4hP?o@(*ruIEyA;ip>|1d_=~Q*-*McROs8c zYX3Zuq`{oDJgCFy$c1J(C(b{C=dI8!rs4##@KKsi9}1`4FT)hm5wmH=2yZ{keWmBu zRppw+FqSZW9O`#VxCvZ$6-Pe&f#W?jZW=PLh%(~cY% z-zxH86z1Wl0Q9BGp;I2+PPQYqOfMaIJIw?>!e+vLhNRCHIPa?pg{Qwnr(RWgj;TMq zU-km~%|VXnR}(ZXo1?z?3Ig{B$g)e;`ge-RcMv9q?e=ORdm8`VO!Wt^NuL9=o&{Ku z-2qZuA0vm{^OMzewRUv)?b6?O&$S?^R+)p`^YeO9ebw^Sok)r%7xR(jOnu@vVc+c; zd(?{c@q{g--MJkL{UT^Fhry?5@0-rIx6ilJ_Im1ZU+RCfqoS_!o@fh}E;3_!jD68i z_m+mWGV)UjBGm+}bnpRHsEH2OF?6IfW00)UhEx*NC(9GYW%82!tMb?w zZFfyo)h$0+zq67^ICg6*rh4+Zq?xx}n=vu1(mJ%MMz$VNFA03>Yc1LsSJs++f3+^2 zj_>h#{IsOlYvFKB8|ra>k5IQO9zX)^g|(D(2;V{-!{Ug&hvV$zIL=%6x=Pr|Xk z!VhHK0ly`a>^tm+-D$Tn!YC7%#Dg=Q@+(n4iPwQ=p2#?-yAxy9$g$-hOQ(ZSLQNn( zeS(`N1t$%@&Am3oWSJ2;KKSJvbAtiS2h~YL#G8cv9s{-f@@CB7+;epCUu*y(-$5pO5LSfi8@Yr{IQ zIDFF?Bf5x}AEC0Y%@W|iSDo9n9&0DxU+y9)r`jE9zGDq*x z$^KOyel;dm`Z(H1`{6z+!s#aFrEhR~F3q#dAuTpzuB;Xhg{G7IXl4 z*RIWwnSeYG`A44rBhUX@p7$Oq#;XD&?Gdn@`M2bG+H-B|DKO3l$h~dwtS0pu$3xZF z2OFb}2QT6sc2MI8aABZ7Pt18C1S>gqkUKNK|9DKgeQ68c-zZhc#qE{J!FO=u%@Q}U zqARRLIcsQYJP{2+mwhZt$cR^spYfDg$SY}T@YQX?=@93TJN*ew$iMETp z3em->k9C1?4fU1^yhlWn5p+gz9V^+oibI}FL~+(!L>(Wv+ngi%?}Pz2wS_O65=PO@ zoWfHaTgC1gHZ#&5;LSkGsyWB!@{H*jJxDZ3FH@G_FlVVq5J7?#UZVD;G7c@4z*kJF zHK!Jr?cYK-etguCw;6QQfvGO>x)GW2i0nvrO@#s#{e$+}lt{?)nU9=Zy!0Xg4k)2L z8;ik|s@aIG`;^}4%ZH00YEQj|Ftt!oGJV^!$DLF8?pYagRmWw?qH8Tb3LNHZ)8-$5 z_xyNd?|zX*@?Nh`VRn?#S|r%x*!EQaGwXYImWSn{$0N#~78m$lLzMK-(O>n`$M|Gg zsEQK9?9$_rFdcl@rz@&rW3Y~2Vx}Y@rz89%urITYH|>Qt={pUGJOl$tvsxfM-({N$NQlh8ufd=j|oS3 zF=~xgLK%TwSie+i$jP>(a-CkbeGc}2BP>)f}gpK8`F5UFnym(O5jHO?N`6eu3C z@IvPlfDyl1HvOLy$Tug%|56|qT?^!BKpSlQZH_MOSYutvNH%s@qL6T4CWlGTr;S>qR^(wtUgn6mLOYO-uJ0vGusk2kE)8 zY1TGg%h+MhTlIJ0%XG_?ih7v*h}a%=Cx?mqZdL|vU1UWihVLF3_2P?ISqhM$%EpGp zrXH&>;(V!mkU0Ihpou6q#T&+n7Gc1KhcLe3kx13k7nMz&mE@Ssk2`8`I^8fBDQkD< zmLx1Ss?Tt$HBev(YR|^0&L2@s)=Q0^`|z?iwuQ0O2`_a#$d}#2Vos3ItJNsC4(OTS zEbBlw)?a^(=UY6oEw5F!p64VZw3U#8ODn>O(UO^L(mgE?$BqgwpD_1Y%A={z!PT~R zHpn!aY67r9;c&8?rvY2HUqPWoUxL}(KB z?*1aJiBnqK2}5MI%WalUSsub$L}6a8sW+U(y5 zv&pp*9qu9Kwh%eG= zcu3xm?n0v@@PiQ@ID^q?`WyIApPka2;>n5U}WYD8AQfOrObENoeoibpPY zRHs&B1PM(xodLVciMZUDrV<-I+q-Z6fk8A&i@WCZ! z=?T%o?fX@2S@vp8Z#ahoPNumrQ8CTiKv{!SV`<9kUYdA@hw=+3!%8pYn#Vsr?h&XB z6V4JCc>M_fcvnS*o*Q|*vEL!&peIReiKy+KEqx)MojQML^K>5?wtU7W z14&O3LNki$X>$)E&$;tsB_1$qeOXJPjn>wtetSIW8+%Y9o)GfB=FRwQQ9i7uuj$7~ ziAJ*v)gtFXF1EH8vWVN%So;R#tQ^lMohJ@?+xH6&2%aC-c5!Mgo#_z|-73S?ukXtA z;4$S*B)pBZRvG77=UUHnysbW9!*4n1?R?5#Z}Opvg;?ypr*FoHVbU|$vgfh|oL=p1 z%P8efx1NHN7h>KwK4qJc<~=!vkDV}A3I2*kxlgeBtJf7(6{ggEZ|V`DLuuieR!>ZV zbl=^=c z>bNQO|CgvE3V>B0kPm=5&H!Bh59;{GtoIjYz1H>Q>UQ9i6EkqA=x?Ep_m5}nCV2=N z=aM}UB6c))(Z^k&(hi%*koNFe-MsTl8&YP@~-o15Xq%YciEEr)Y?d|*abB+#wMkJjEa+xM` zP;uGV2Ae+F#^xJ6QFW{i9+mQD#?E|4iyn*`$x1v9qr)s|v$1zmXL~P3Mk(O$awqLJ z!twVd@KgJ$Ce@V$K*OA>7x_*PN>1srSXRY`n^uosFs2&rgL?+gd;BO3OAGHITMsv` z>U=2!q52IBe5Gy|AKKDT4I@e+CpX|bj;>4h(!+c>p&HXIXT)5kzIO=AMK72x95oGJ zuG36wH>(!)^er}zvufVw@DukkhIqX-t@KNike3SuPIL8%GuFh(N9se|a1{%WQ;OOq z67Q1UnoCK#Cw3eo)vBI0Ki;}$g0+}L@{uwvKm_9>=|Ie0!J=)@s=GyUs&)U(rpWDk@5tpG zms$6=&j+`&@M;^ny)aa;6B0u`vP$$~lg2YSD$damaeG5`Td@6uMBQ=`Aex?s9=o#GtlaSgv(_643Z6tkZP+ zJy3a^j2Z&S$qi`(^zLI~`7Mxz9fa&<(q$BGFMF(%&ru%hqswe%&5X+!l&?66ou{`B zhh7`T`)UQnuA@?8$-fG15r*I)T)OcS+&p4Af=LY?{NTRBD)IZb&Ga~YDxSQ!Wq zE@z{9kJIxtYpGTlSBRVf{9)8l6iGBk!Zq5^UHfm+X_QWfzXDlK{ z-XnjW%h5dlwnx(HSGWVXY~Jah-p@k+bsF%d1o~fUK%(p8`8I$CbOcu5|7gH}G~i!o zz^m;8T~R3D*H-~!=-<+S;fmKsX&Wf^&=Yos84>)m?>={V>Z59Q>hr-i1+lr>N$l&f z#WXDJdKSL9KnCHCOXCwq$tqP{leC^wJ+2j5^|j8A+xFj@-0SO?8N|z}OpH}}-EmKf z>KfF}7=XFz<{TW(Z}ADd;utQ7P5w!P5RrZ5d0tg#)-x&1=`gH?u!ab{vHjBsmVPzC z^*!RoS4EOV2-N736D}Mgd3O}AvaCFkrZ0G{NL9e8x!7nZE@+q6Wv^Wp<+Tc#Av)+PNSGqN+(DuQFWpHp5D*|~>zUf68c!Q4P z0~VVb^yqTh-4S&95U`S#!B@miZRODo>?i6%89HHW(ChOnibVx#%C{<%R&HyzlxcP% z5ccrWHONO7bda)t((asRy^Q}%^xp=*Z+8CwQurhQND2hf0ff)xKf>pqU6{Y#g{e}) zdeR99pJZU*{5|2L)CK%b2}T3{st`s4b)`KGa;u_Gev}LrLis+FrkN#XmXz=G%fUHk zX=!OP-lf)dJEuB!neEc{*-$9pnAC`!`&n_*F^8T=(*T1?l;J7>>IK=e9(HzwPjv6CjWu3EAY_{;? zyOt6QwMM~lRi0H|n{FBfom0vHeerI3?u+0B3{pMmIx^C17R$S8Scb2O8OkdiQ5Pi+ z<NFmL}RmFGA1w0(|51xLOr92OtrF@cPL2iLifTq6+MiMUa+Fz1Hx z7`wSC@Vh92P`}tBxOvj*rk(uv0{&tv`sN_~J>!406-|1*^YI7}f;@mV;vWR|4+8rO z1lE)?B?b9WF(KgY(%%w-n@QI{i4eGhazKJOvojf6EEYbF!eQwa_t1Cp9272ofBix3 zgTcs|f}L)ugF8;BAHtsE^hA)$EVc9Qkx7nek2-!(6-`y_2wCwab(8%hyPW5d;WGE7 z_Jv{;VV7YqJ=KTzQ;3V6$=@!99ND>4yxO_VObJbmmEPG%TtJ*O)@GC zc*kmY_}0p(Cnq22_|^TMPGYDTaqIrZ)xZIHq_%mZU9+<)ioHFztc^8JGBJ*Ex6dMj zTrL699t@)ihFfhG#~k#ea^XFpb8_LNb#U)|x3Z&m@()+X)8D;eXSSqDYCbF?>C|0M z?`$Hvi+d8XUAt{~OphS;0&@FKWCcn4el5%jDPCCn=WsfED0(>t# zL@4}%hE%kHg~N+LT!DCyJQ6mndez8ctofyf-yKii=6Ec@((v?&hg3Af7#M=^RSVD| zB|O`NcZ57sU%#4Md#GYGm#iYYlxL{@rb3imMubi3P2eNVPaF#K$igh|7-eg89?eF2 z)J5>;Usilhrcr+0Mh7(sl2{k@P{8W9MW@5->{#B+esW^Fer}oT42N7O70Y329|OPe zT%K#^@tkI%&J&{m=oS| zx_^eFU;hB0KWM$6U0K|5cp!2;C`5i_ z7Da{vUCgKW7SWdO5SWUH=>z$zwvT)k7`Zy4ef>3FBpPAGw^BEC;}pjE^b5*At(ULe z3hVwx%ZI6H;5hzDz=D`cD4xwACVO)S35)ItELVGtLjU>>7Sk}nAme^pE2mwnV@__s zmTRUKWk;epzSXMl<1o258Y(SD4`NzU3E?`V8;8CrX;B6T4zRe6NZyL44sbh0;iBo>tr}6bd(V_GFyQ++@8QoHO z%u+wRsc&Y)H}=`t=!v1#GuCxh ztq|F!gUZ(a`z#nY<=6jGf2vv>@0^1*{iOk^Zj!R2{II zWWn)#QSDuRF&l`XLjz40QQ~fjf^`7%q&>Pg(sJT%wC81MdXf`~p~$4Zc6h~(K-0=J zHg#O8Sck%gYTDeS&nNXknAplj0poIei)Tk>;V~8B2L3WF`E^F{bKQd8DKCe2HzSAb zj4^c(<6>!hOH@p82ve$JlgtZ~d>SK$sVa6KTj$8e3Fsv9uOGYRyrc|xXW6tJ%gmkn z|Fw56a5X0XAAc$lqR6Uc<)DO=+{rDrl&aOA*Rt zi6se^GhUzPnKREk^USOW%8uHqWegt#R?*ROMvWzek_we2?2H*S)fTbyIV@*#&naxqWohd%nQ>2xc%2q)-+Ci|tIlLbqUdn+>;%({#oi_B9QW8R9k_Xi z{yD>AXH(~%+H~`$Ua|7_8Q=8xoT=MweG=1BBlpK;lKy&MtTK%r=R9QnlgQy+Ok166 zw*1kueGXk7>@bM#qc*bV)9kIswIZCx4l>xbqEE$xxJF;P#BDY8E=3~;)!Ms030 zml-K*Uut$W30r((YW_3c!y&PQPl~?pJ1(cvAf(CZlA3@^9S0m~)UicL;83wzp5x*< z1}5JXjy^hf*@LJqt@^#R?aOOd71Plybb(|>;IV=CY$RdPv$VF)cg&kU>vT$+Pj9Er zKB|#eQMBMx@XWGD5q4W&4({ZCcih#Zl4XlqE2*0H`xbA||;r3^MW z?A6J0!IR>EuQ&O5kF?w!bUAm4{>H|Jw_^K`Htwij{M+@@cK2HOrSwekGIEb1`@Kzn-$L-HXYq7H%o)wbZrw zh-Q&H+%DROWM;K1xsm$QCnav}pU;p8XXu{R>>uG4KY3TQ*TV#_wA<}YPOXSv{r$lu z-u*pC3^E%!yT#-t)>{rquN2w0vANK$@f3x-I5KKR(X{Ku-Sf_mn|ot&hPP-<@R3LU zpX_L+ePKxG!V%}!7r8a{sJxXW4*#t4`LO*{o{7gvW-az@Yq<2_X_GHbisDRaWSd)f6PF*W8U?am(FCA(>KZSq}*?%&QFP;RGJWVK=HG1FD9 z^QCnSD#?uI6= zI%b2*YRre3*bP|kz_&e@f2Y?o?Kqy+{_`PM?ut`J?`gGT$B_fOcE)P@>bwwU+m8@XkAxRyJW!w|5xMfUomas63AbQoEBy2_&WRj(D{oiEqVv! zof$mhm_^p`elZ75E)YJA9M{W4uJwbX@iYICn+6Qjck5QQ=zdK1t*0aBp1kszl%{(nWc8ckRqnUKXX^1w4`a^mbIbRi)O)?daQzl> z>sr3_cQ{t$Z>)bq`^Lag(KAmqJ+1z8b^3wfyNY^y7c|xRTK=1--Io1j1w&sa8EGDF zd$GG~qUhL^PtK>FIQe;FW395)YtF0LcRzl0gEqWo2GATw9ol7^;^Gn4tKr#EW7rPMDA!f zov3n}ap>WYkgIt;4_z!tFwFSb`cr37i|{I&_D?^(T5+|c+OO^MVYUmV$88K-Iw$(u zg?+1Xs|+3VQUhLi?=&A!BfD`R*Zyv{WstoQuiN@Hx@VYQV@C6ve_3^4<FnMjpJ6^p8hsv#m<2I zo@>=63ffjA8Jk`UZv3jF)gsdgX_i;+|8_G`O>bbh`i#WrPMczTj0hj*bD~AfEc*ka zRya8f{n6Cl`+iRIna_p~ZgM-P+%?5E{)JGQcx_{=FrTbDCu=UB@C@4$yF;GAf4;>r zy<}w6zAuE|-EJE7aCVu`m`l0iUp*d(KIxy;?bdJhf3n+M%|8F@qTB6Fpa1Fe|NEc+ zOWhjJt3kKhvZ1p&No%i)@dwvY6+6~r%Z*|??{f?T{?6}^vf-#Y! zHXX}|)!gRjseUbD^}G=eA7#0Og&y)7RAiC-V2W(<3(IC&dV=YfXAf>U)APoj#}QuE zqem|=EgV$XyTdid;o(QtEV*}C<9fP}bwsD2k!MbvHJB#dV*W+ymRCpCI2_+UdRIH| zo7-KY)+A;{T(7iPme|wR%Pca_?aNbHu04N_{nGDTaJDp1_w=Ctx>++XJBP0ORm-u* zHZ^|cD(gN&nij3Q63|xHXWM|it(xfN9#uS!(YkUxN82n{{?jM>R~=Av&&eO-yRh5f zHNDP;4E3AYW{0U!(8iI4!^*xdAFt-ISL?OdEu^*C!RD78{Do>ucvl0NkcX9e3a=9f zqvZbkhnBD1RXWf2+~q^_Qmw-oo4q62B&{{y8h+6(%yPGG)o@c!`xDQ$nBC~t%Iwav z`x#HqZeJ1Yp0_mK^!Cn~?F7ONJ3{@(-gZ4;wrf?en{0l9c4F=tqm^++KU^p|dF_;j zXT)8*3t{p_{Tttz5D>#ZchWcOz~z?bETctEt762LN5$oJi1X>&e@IK0i;<%F+MQaR zjSpDPTUe&)GRe0}Z@{yPK&JG?V$12FUYpd6w`sOfAAWq<#G?@oGcw0r5A+cK_~7x1 zgtdEj4!HA`cXpfJ2VeJV968Nkxn{FeHLd$E`uTcHz4zMZ>kzZ;4-S`foj>4dhW>+| zgP(;wx+ATLzW204z{XTzTjL4qGS{$F2bG>q1+YZ{c7B%YP9Y z$-+yy5AqjlSdO#pJu_iq_Q7$##PIg=bwbOBe%D)iu28GIuxQZ+qr;iu@%vg8?z0{C z+-vp5E_VYRbqn`B(P)37&>{ZxisLK4yvX+qurv4Qk{=u*GT1mUcb2`v@?7~CN%*0? zr$z{dq<$Nc^mqY3F6xkOLTvTiu_r^D%D(Cl&jX0kFVBo5yVgaPJk@D znv5QXEWEk_dtU9rt2mX>z#lD=g;$rb#~ZltD$XI9B@0FwAfw7J5UV4chjgA`*-`Q> zWcE&|TBu<&79U;2SP0aZNG9PMhFQ$|rL2DmVq@h(_M6B{S$`I`jj=c@yL^NOS<*Ek zo0LzvK6)Vu1u7yKz?{YJ)>Woa20CfqNw& z62bvu>a6>*w{{h>zv2t4x_+fy7^ZV0)CZdrk{e=7Mh!G3DQhh1m0{Rh)HNnd;Spn+ z!ya#-G2tAgF+D~;RK|pGWn=lHG39r8e`6t~dRt>@s4>-D(YU@bk+pQ|8&f|vj58+I z3u9^tkK+QKhfqu!ib&`mq-+Jm9Jy9N%%l&z%93l;mRv)vd8G`o-ZYb0M(-~d0d|jF%2afr3pE+VH_K-)G0b7 z2a~)6yue_x`txitmVm_9o96SpgZ%;ob9f?r=!%E$X7PO8Ll*Gnh&+|#%y1r;#$RD8 zd$T0yVSl$3G;P*aS~bvC0&8tWh`m)^TR~4lY-Iv_yn(iYbCkAHg?uPmj5@;KZ7Wvo z-rr&f&$=zAt1P%iZNcAe zDo)+rUvP5qx&{CHO~s02soqlIWbFh8kyzlz69!0v{a_8gA~8>b#nRpV1gb6t|LgoO zG>n_`dO5t!Fh(@cFgCFbU%fIOd#AdFf%_vd4Dv~9Qs3R!QZ=nzR31O-IQ7~k@5%jE zX&8@iT-iR<5&mw&V0yg2ef-siVZoN}&6^0mAIRAG^Z33Z2|mcDv2f@k{vQj<*lv#cVJWT~E2j73Q8lPcfcpNEH4@}SDzGlIyItA|+R$HPb4#BV&J zNjkN#70wm#i1F}(#0%IM5`nLW5-eKvUuZ@F!G|W*HzS4->yx{x8Z!%9hWT8Z!F(qg z!Agb+{)%B%%1P)m9q!J{FcU5Dbu75c@m$WyqdfPxPeQ5thzZoZCaVIXtE}fp&v0|e zrpGVGd+F}_N{cZy*GUR#JNRk{UX-yLP@oYr?BClWpd(PX!NZxuw z07%K?xj*ZBo62!k(Ffxhw ziLLa2Y@snb$b3ARktbzert64;tC7-cC}5Thvxwc2drNZ`GVy7ve}-{3@ik%7B5_7G z=P}1L`ONN`Mle1SkPYfD)htC;>`< z5}*Vq0ZM=ppaduZN`Mle1SkPYfD)htC;>`<5}*Vq0ZM=ppaduZN`Mle1SkPYfD)ht zC;>`<5}*Vq0ZM=ppaduZN`Mle1SkPYfD)htC;>`<5}*Vq0ZM=ppaduZN`Mle1SkPY z;O`?)U0scfV_Q*C!FjMvOHWtj2;n3K8wVRM9P#1EX$VlJz#XF7znwr%UO{?VvO=Li zPLh+8r7~H1T3UK$PBJ#>X$X|bB+pRSP03yMpN3-bQ<0@4gfW%AO3JXoGwF3-%&$;r%2M+KwalS!qc`Vho! zb5Qj;IXR`p#fX6VMZKebWinZDaWV0b(-EFW8VDLkS~@xciuZ3PP*707<$>##IX~A^ zHzaSWv(Q18mXoq)xjPWc+p@`4@l*?rBW3a;| zm6wxQmjH$}I2R!p{vaisjXnW^#cT>liyX?#!8KF^Tg-Rx`FwIAsTAkpa)?t=U5)WC zJQ#W5Lhz{aj8Y(z3I!QclW_JYLNKR-@v@JP4@PGgnib^bkkh3yB#WF^SCCwBY1T~* z{*T7_NC1kATEIaXDuIU^U6QKw@hPo%?ZbB_fz_x=AH-3}r1&Y5D~bzp6v^p%Ib<4& z3}Gs&xzq8po&)XDVlpG)!{^sMg!I|j4pJi$l8xV8acKp2AucGdxL6^VNkUQA#Rx%t zapA}k=RsqDhsy)nMyN6z8Ivccam@zT%E1skClB*3h>QYq(SA=&nJky)$FcPZm7+$u zvhYa(NnOJg6c?lTq<>~B9U&YCm=YSlv$IQaUQVb~Ud;l>(agL8SURycsg#?cBB`(r zjRD4vzQBIeU@iitrf?)JGY27D93LOjv{Vj}<+3+w9PLmQ0@LGSs;UM(11y?!LtG=2 zlSarn8EIXlt6}Rm4UG*s=MHhFs}k|?d87H&p8qf3sq7c!ApYu7w0V$1M~V>CuS8ku zy4ELGq+xCfror`ihzX06v!)F{dRlB%jIPSl=0do%qteum{T)zzha3}~erONY1<5Af!fOC(^Y%fj9 z2Sfg^6jNnE{~@(M*zX(n-+Cv0kfEu1kN-|#>Sg60{}V4|xQAX?0hd|4%0YXEIpF#E zPi7w$gLF_G+$H|QdjZvKGj~Pw8{{n3aZkB*bMiqAPZe49{9HFF>Zb%K0ZM=ppaduZ zN`Mle1SkPYfD)htC;>`<5}*Vq0ZM=ppaduZN`Mle1SkPYfD)htC;>`<5}*Vq0ZM=p zpaduZN`Mle1SkPYfD)htC;>`<5}*Vq0ZM=ppaduZN`Mle1SkPYfD)htC;>`<5}*Vq zfxn)B21DK7L!jn0!!%>m|8Q|Ngs5Ns;j+TKy>48*7Gv7;0dh}7I_ zNC!%S8bN!Z#?ZG=6X-ak1?56bp?s(rR0!!qH=*WG8AQ^qg!G^nP+N#xtOa&j*lS~- zfIZ3a9bC*1dlL4AaE?I27YhXbTo}m*aU_2o61Y9NHu+7CDO=pz_quF;5bd3NLu8bc ziOAaBS2$ZNWXn8KEOhr}TK*o&a}auZ1+n=b@8Kcj3wV>f1bzax;5HKj+)037eh_c; z{2;MAKS(I@XIVNf;CTqek|17yNa!ELCODE87%b%b@Z2RRE1NMpe;(ggBoX?1GTO)w zVNQ4umY<9KgCxAk62Tm{sHB+W5Q)NFpM;P~B%uWEL|>%JnKnYL+ zlmI0_2~Yx*03|>PPy&>|M?&DajsZ&2268+8RmsbEQmm&M8qAm{WNb?vB|r&K0+awH zKnYL+lmI0_2~Yx*03|>P{QC)zSHR?5I(Z#T-ivEO& zngO{&Ga)x<7UT}ihWOANNC0_2o{$$LguEdi$QSa1{2>t(00lx~NCE{x!O&c29yA|X z0EIxIP#E+jv=9o1BA`W3B(xZkLQ9}1C>mM{ErVj9<&X?o0j-2qL93y!pjapll0$2t zwNN~i0DTRugVsX|Xalqn+5~Newm^vx;hKbfGPDia4y8alpq&soo{D`Mv>Vz3?S=M1 z-$3N}Z?!sD`x`&0dk+C;v?LK)4i7EgAA2#|VgRs#t}Uafro4m(&e2`5XwLfaKemec z8P#^Ud)eEPA$6q;w?>N<fQXPuJ$hm`-% zUbF7`|K{uemG-aexn3r)DO5WGcviIl{|ol7vb_(fe_rkN|JUvR-*o@4YiDosll#BY pk$Vhz4j^H~Pek1x2~aM7Wb(fh!Z7c8|2zMwR&5_icONf-{{u-V-7EkA literal 0 HcmV?d00001 diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.pptx b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.pptx new file mode 100644 index 0000000000000000000000000000000000000000..2c27ad1630b914d4f731dcbc905b83e21c583755 GIT binary patch literal 37911 zcmeFZRd^g*lO-x zC5j2R$w6CPZN}Kpes?E`u*OjSy1>3zJ$7C1f*pP!S^Y#KF>JR>`94k7PxTtyf5Uu3}uL6%BPj4dMC05Nlvvk~l0&5_=zfpetqNps$n$xU-`> z+^N!QUN*nxuBgPU+aQ_+8NCP|yz7@3af$Gi7l!Z#npfl{G zM^q|Pkxx`|@b-^?MztVyael%rGEMsjJG9H>26(+6 z4Hbg%OFuNWzIxaZVzdx36|!?c$ipBc8_wzIRpnWT&_A_{AR zN$@##A?Ez$B-0gLwpB;bR&k1(;m7nngJE!yQbOAqynt8ag;1Nzh-HM*RiY`pT!sQ* ziaBF+5`munolu&S5LJ~pC>v8NM?E|&$NSo17(1m_?;fZ8L)PNiJ%8F&ZO(U^TRiu$ zvn0a46iahRqzx_ZSef0$SB`j<*;Dx7uFx<#Pt56tIScV~8UEF0t|&%-aDDpAF&q#O z+UFAv4leW#PNvSE!P3yh(%$Y*s60#Ch}>mB8@|MRz?ZVaECoDB*CU+K^##H*aUsTA z#%YnuN~7rJlx`)l093KlA&+#EKAzw9Sr(sf+e{{*gK4O9Q7$cI@U}HmFn#h8m(HD% zk;JJ-okyt=sM-R^rlKyl4(_Wkh`xQTm|pn7pfhP9g&?)Syp*y6EJdAHfx}_dA(gDM zqja{RGwqMnZBoNeAZ{lKD_7O?6zSi*hK~JK(_kENoJ9nZUM@@!sijxApdYb#xeFkZ zl&|CLmQ)yv{2Jjh0Dsb~<|*YvA0A|a0zM))euqF!;E3t`i2As8 zNG^eO-|2hq`*oPt^M>-#88Yssi6Ttb@?D6HP|j_r#@9Yl#etzgTB?9d=B^LyR|<&b zL``6QrkCKf)_z@~cnQEk4cFDME?v2+MGyA%t^os~&}%y&PLI7Lvm2V5u~sZAF;Z2E z0o>FZPfZN3X=S!$VwjbbtvH<#xP6y@u!E09JPx8oIvF7y=Fr~vSB4crbG48W8U2jo zrr^;dFfMz0l|}+R?9jN7JnEZKAE)AY$>hY!re;P}Z2SDOU#R_LTw+}MY^n>yogC0A zR7NK%)5?y=499wEnCPCnMIgf>IHC_6+L-z5b(;{@lLcj}{PguTc|9h4K#dB`_c}<* zYgFZTAPMFAfC7a0ONxYNbSxlCY7B*SP&CM-omjha4FO3=i8*Y#u6O^2r%z-1S6hoEz9|~`v^6G3ARzRAvo&WMOA}LP`oAuWe~fKhRo6a? z0nInB>BHdq;szOn%t}izuvBtGi|QEBA$smRFu;aFR^`5Dimk+Q$!=7-xXWSUzH8}l z)0rV{DGUJYO>HvKo`I=3FaxrvPd5a2aVEpPXttyKd%zjbrueZFLa-lM35Cal!Z}+8XYu{V3;~6*Fbj zb_0{8S-E>rFx$G>k$u2{Q`yi|`4D(Yd-Q}K=&_W$SgNdP)vvFD<(jUq_JQ=zIdyCN zkM1DzDYQDQvID}oHL%6(K3!tvLn3~LC+#6SN+%z^ zTD^ThnMtyw*#dF>7o#U}8(X28Yx{7H>fDtjR z!?Nt-+Z6ODji}PBkg6yU;xK)sqDz~Khic_@%6-=Oq1#s+n3As~EoNX;=BYf;V)2G* zm)(Z7 zco0{%5}&@X91y313iZ_s3Xe4CBAoj!14f6_FN{I5hyzXFCL_Z+R>|Pjvd52{~#g;GNo0&E@6@Au2AO0ct>xG|rp8iD# z##wkge4+C&|1F+?#m|aRyTi$i_JDfOqQ0g++Cq~Hyl@vfdOkmnL*^AV0dZx{e&Ddr?f)_6CyR;N$dxO)0nwV5KMle>)1s`c%p1AcdxTf~D?9Nd+!3J*g!7PO~X?mlB& z{=;kdhz|Z-^C}AzkAQ3g8uN{^RG=;Fo7P0D@gqeR=fgJ2K$k>JC=9Kj5ou%@=j2`z zs6C!^3{504dh*pcvtlB^Se}0AVB99sNv0CRPqvdPlZ^ncY%KU;@&3+qn!GL>ri=oz zZ+>WW#4vAobi3t<%4qWaG!huc4IbDy-<=7HtNZXz@L>oACHw|QA)zVfzQZl$u2qU7 z&PnKjx^B0NkKS2aonlryo!u~iTje#J7$CGQ&Ei~FrC1ds(AM*>``F+J1zFU$c?I`% zq<~h4m64jcI{E@-iAjKSSeG${-0x@1or_&u3%HBCn{V$x@ffQdp90xw>m+72qazbN z$zO*_h87I61PdaWBhhy1Bq4%ek>z6+njRfG{${^sU-*4=Gc{6c4bsiawh!8hc@_}} zzZO&*hfjfS@bV5JRon9P3anz|J*%U^*llm@U&W>%H%A83z!6d&2NRz96OAs?0whW@ zTp@KRie?<%^rEmOx%rx9G@BRMuyiHh0b-ha{p67H?j9bI!jXriqttVJkq9UEqyc>? zee2AI`Q9)xd_1!iJAd>3$jEBRk^dhP3~Kg8`k7Dbm-}CUKJ(w8ul)z~{r(1hC-`q= zmgo}ccUl7h-|&n9&paVgzNjGssm zjao6aDB~h=tnRqC1hR2lrF;4b_%HZYWrw?i-n%Pa8wO0~9&5-IFJysD633JKB|A9%)Hoo&CLmk`p`nrV(xih`q#yE!}{*hz?oW?W1wiRUbMNF2-#V_ z#oU!oQ+@Uj=&}#iGuY+kps(enY5olITgxMQ!0WizGm-3RwNJYfyXf}*l-nOl^D+zU zk51VZScesgw7~g?FRk%bM;2c*%iZ-=6&bZupsSi7lD-|!lWeNx7*6r#L5*2B40oT5+B@kF8;f8@(S}ShdQEgp84w%M zVuuYAFdXB9J%4XweD@|O%IR~zZT|sDMAl>Qqy{p|q5!c(&>|j3AVc~LQ3kritz;%X zSNpk)Zlz!MVi4)`Y56lne_KBNI*$#m5aH`a9G*6tAKLMcJfN5&jL9LinBW9P3D#}J}2v!)E zfSlg4Fy^ul-rQZpVR2bs1dHR~Ori3a4`+XdlJKxxxVCh-UOT9*gzzqlp6|6kmLsVtewx<-tC94oZOVVE-XN$BDAp>Gxsy= ziN2~Ze3ZVt00c)sCJ$Bh(VDv)pO@cEPIdcnqQfqz^?EQj_e$3;YSH-UCuF*5U;7~00(Y}S(bu_$&d#swOjxVIW^ZqvtIA7;r4A4MjG#WHO`CNvGMs??l_l`&>= zkY~oLWH3y=%1WA-FgMtJ{hHF6lDvw)rYBL4!9J_|vAiiUtzxD}V9?5JeEqg##NaFr zaK-&Ve0+6`!t97St77T;wlT}xzqPi|!qq+(a3|$7o%r$ss;z`csPSqH@}a7Q+#nvnJMp7QCr2oHA=HirAp(C2xvtnp7ZC$HE;R2=}8(z*$4gQAwncDQ@?;@gAWR zmZUuEW*=6&@0dHdJyfFRNh}X(6J8-MnsGbWVmqq(#xggbC)bn_&U^)~fCRySNm7|3 zVw5Ek6z!Mk3lCiHJde~;2>%^73m-`S06Ikvn!!4dspGk$cm2_iCZ&0S%aDu&05i_n zi*yU8jHki3P1DZRH8TDf7pYE*6unbl=?%fZ-8c=E|Y*( z&A5t53{&q zxu#Apx=_;h>_VuN9dn^Am5!jFhlL1b$-;b54_35rBKGn8sT=_Y5aVQI8v@Uc(MAs%1lUT0I{q>6VE5(+`cF6dKXq&v<<%bXQtAhTDL@Kuo^NvLq6&b&GfLmKIY zu8AVr6wXWbo*oq&Z_Egj$c^c2D2oxD>4}QSZEx&+Nd#ULx_a zoNt?xZ_F^2;PV?f-@_r5jw8gJj;18G0fMUMwXryc2KjrY*2JbQ-#^AgV;$0_-p`T$ z{nY=WL;pT?tNuSz=bd&(YEf_C4o0Fer2-c&jpyqn&aqY0M&W83uqTXRbQ$?G`z76r zF`D0$jd6m)UvZJpY+U%Fas2G`FDa087&x+xiQsV=b;j57J7%Mc39f&kMpb|EGI6!~0SrfO-D3S`KgZ?gWgH41{9w87E@E^(H z?>GKmg@=Ea?tnjq`p+Ny-+go@Pe~0hAPrpxUMd{e&ABbzBW7KQ2J=D{oPrLxtwPL^ zkvTTo+b=K7(n((_cxBUP*!zt`Hg5W~kgOKg>qx>_W(x#2D11p{4QbifTc8zLWYlEJ zCsR6_?j!H%)adzcFv-3WYK`lOX-UO0dF(A@Gh;AjQ^g|np+q&ubSSNiIuVJeM*1_+ zM09)_^d`Z)iIyte_W1CUE9T=`O?42~M`6%BYBmR3F#A%}enciFwlAM2((-H~AlqC+ z=BEKTJtSP%jrxQT61%d{8K)mF?taT=(47(#NtPw*502TN#JTCN7IF%)yGPXH)+x=lDNu>f<#{WNe?XBKXO(i9g}u548LhMq~^U-Dy}FMxWcUg15aV8cW-pnLIw!#6P@*@W9N zv&|ayX{e~ox$H9glGO7ElfL$Jbk$)d&6c@wC&F60^XFQDO^w;Z8atfA+KK5Rn&twV zkJ;AMrl04-tvr4ETsQS(W0o5&Evxuko*A?E7gP5W4&Uxw*TuXheEazh)2}Bo72=Nj zXe6)S4{E+bw%KjZYpyI8_HYdJ=+cd4Pp$dr-ije4LFt()?OdvxF3C5#hK(;mZ&q4H zKk7={lqk?RtmRld8rC zchE88bN>Ro=99u_a4tjW@W5A5h|}wfjgZ;rsXQ8~%_Ryk>`6}{Vy!rN78jkrERPK# za}VTZ47ZCG7C;(2h8(JUrh_jVJV^UTA!QtmHX-bmxw^a9k~ z1i6?S(rX1dMhoA=5j?}9<0`+wy` z81~uqdFrHYs-<#Cr>Umis%n>uTIG|fmPH}OV&w+}I|4Bf1BVld_PQJPDMjyc6v@{r z(89n=jGcG~NBZLI_Ik^dO}pz#AYz;>&08*HaIj}-te{y5FX`?zcPo^pUaz9k_g!4I z+gCOin5t$#%3{e=8`?HV(axP3;wG7rh9U1>iuAA&iXXms=W zb7NoEABDsU*?YaSKqTI2f=K^k^T*h^=CxHksa|`3MYb%>qZRuq^>8H5RzHpGG~yP~4}Msfl+$^VuTq-uTas)PL&*NG^-TwJmAc&WQv)V zQ-BG~@1mk(PB>j)<&lB_QXFhm@%ixl99BM~tw z8P5}Rkfwqdd~cf!mf9d||0ql95q05DJuxWo%4d_b)2%vne}sCBOTGdcwE8KEgoLIb z${`~N#|V}XhV{jxhVFKA4s}yKWOOAcyO3hp0!8M)4sHA!reGBtPE{bnfkr7$WFitc z4O9!RY%P^7TD$OL+Em`}-}|?v{(ij|Mw!R*HB_f1>{j9%Dxk^J8qOt?VV)8K1M|c` zd=U4@N)&gCwYc#I^sU6<2}*Km-g`xtQV;S3j$&^Z??FQ8ltu-o*ZJ{-Y^_>wG>@KC zwIkFuAH9r4l(P1C8HIB&4h-6vXX@qseRJPo|0&e+CyPo|f&l^9p#Jxu`&ZOC`vl!= z4zzB4%PrvgCAud_2E`;8W{+m2RT1uEP=za#<(rlvW=Sp86YFz6xjYkF&W*^WU$6N~zf@1rafx#|8f>7;VY9#MTM<1h(yeU5d9^y4Dnc?_wDV7UZ{SaH z8;&|Jcd3}QNOx=ByfZ>+j5}YH>%?7~I<)k2XiJR2b|udq6IKUxGxg(bB&SPJj*c3% zYu3nHC)TcNB(iC7_TlsQV3#Ui-6^doXa~4C4?}b>?*2`<=fiKq%vY$d zIdfd}1MQX4wotrEI3_GH5+W$^^_0M5CD`G6BK##tW;C&!fj=ILeq4#Y<}H2Qo7|Us zV%BLbT&F5|Np5h3qk<7a^U-<+E@EH-DJg(PAR4+e(~YZmG}E+)0#w%`#)dUca+pi9 zum5PzQnQikwlSrOtf9@(vJveH6LaYqYIObbs2+Z)KGG4Ra@xh3$jhQuNl)pcah^!O zK<8J~oHZRyf#;002L{q73}Jx<26UaQlt<5WR?1)JMq5ds(m@)15FPz7!s6g)I+LCZ=U`z{Nqj3rqD{dZH zJLOUnU+(XoWD(*Pvc#l0v-Qjzdz)HQdBbTBVczgq$^hLl?gNiYb_iDj(?koPjP?~! zIs;8bfk+Gi;Gu+b{_N)8QIm>|e)Ue0&S}qZd35nbB;IHV;mXX{TU4dW#>_fcfbZ=W zhw;cPfjjL6+UA&9B*g-XWP$@pzlje8GsN=u?+t68=j=ee^==QT<+4yhp?xFM0op)3 zj-vg7ZLkxVo=hQ$Qy3q1eJ{w2C82%3(S0};_W1Gku=C!twe3>h|E6j(*`6c2Zb*Zm zD1~hh?;wET2!=uc5dfJUpFBf0n6V>=_|x{{Xz;yt&K*XE}}Bi+AzHb2HC|RupHAq}Z*Ty$Mg$orL`6`RmGz|7Y^}ucSiT)a(ZK zvx@UcTmReilJ$?Kccyv%C%t^2nl8Fs>^XpI z654_XS=G`nZ)~-hBq!U}&TO)oM|gEnK2kDC@==suDf*wx(ykYLatF)x2ggWgI|zy- zZ_9%2TcWD$#GAY@o7yF{17-?q>B~E+h*vKv*wvr67=PR) zU+8>GjehBS!zdNSU{}?i=wNqLh1W(Anl4v+)?~5^73@W3FAk#Y$BPJ935Q@I z>fUc6rv-Adq)hjfanVD1IBvdx$Swckxa!#xg@wbS}%G z{EF&ex-btkhUjp2q$tVtfzJi-|8}TQ{4mEj{xummz6zMCG2)~HwZaouRS>>x5oh6t zO05B=3XNSMbs2Csv*1>p>}R2 zZjpC0;o@pgPr(QCnnL@QE3uXe#WKYxz-ZFJ;~FvPu4720!`S7N)pYC*zj-$J z*`>=jn+VOEVY$WeRIAxA2yQz4YI*pZx$@$G#tZZ^&op&NH3Q4DUFsGe>7H&Y&ZA)@ za|1Zc_=>N$PK#~B$Xuiy)QGJI^2)Gn?4qjV2v$~SJmXT^ETCww{|uyp>PLB)O+<#c z2N7|~N2Qh2m+QQ-GGJRo&-JuL#Gl5owgpp79oAnN$Wo4Ln-{EY@3pkwd#t|t>DvzK zm5FjEKYcreRM5^~n^)VOLqPB`ab!4(ZxoPvS#Mr&)m|(;DYSi#eX;6K)jzFVda<(p zy=wgXMKMiJTJN_~UI@haa3Vwcau8w!CmB5;K3>j3$yI6g>iM*?=(MsjrbnXHAI#Sr z<&@$Vx7jL>{ELMyg$W>@txrj+j65VNTfmDlUU0-Vj&Y|=e<&f|~xn3W?9}^vB+PrZ+w^et<1+Vog+@uSQJkm;g z{S6#lf~_9;!QO!V<<#vHQCL+3;e_w`VMSrK*kE|5Ykq!xYdqh0*G8Y+35Iw^EqJV6 zOUCk{oK&X+`v^5Il$-Z!PB|;+oNA&=RFhNd+I8Re*Oq$xpC9h?2)F58n7WJb+;v+Z z&Lxf=cA+BzT96dC>JwmD@nJGV%PB%mHNVZu#T?t7_fBjyDXSk^DywJsB}GjL@>NPK zfFV4U?djk{c8M%Tsdi4OJUL8cJu3P(&EinG#QZweZk{#Wa@`OO;p=YWd&2yuNJ6<{ zemDCmi$8p3)c>7O!S+{mNbU4bB>DX(l34U$Zlp^*@xzMPzC^lKQ zKus^L5|ql%3QI*cl7R*iW8VxHAxyo$dW^H%Bg)#IwP97%=w?o4*y423wl`SSrX37d z;)#|pN-U;UU1)W)#Q896)%d;d%l2TM++^c-Wt>gXJA{{K|>L1QjVMRnMZ@gtEewk-Sj&&fp7}r|m3SivinGme^XdIdJbO+dAi~ z@0SJok7+`^C~=o4+lO!fD`edTn5t^bPhOhgoH6Xz@2iEk9E88JXXI3*Z?$tf=BJ#Z zotBAhl%p*|jgvd{lG_Y(TlE|Yt8xq!p=uPL3$s`D!4Q#bD}-ExTN2Dgmt^MCu^|k& zJV2C9pJ`>@X>PlazRXV&a{-9GkiHX9_-&&YdDplzvjREgs_TZZE^wpsfim75>Rx*? ztRe&`wZ^t?SVbFDG;eL1&KI#0oJ^FDp~C+9=C-d0{tQ-$(ICn@(#*4{<9~M|UP=rU zo>>k%rhVtg8~9aSQWrfBeg(U;s^~fvJ`q-UI1CqFPvx6&1~u&YG#5C)Y~%yq!ybaq zG!ZwA_3IO$QaXIMTM2=~EfKssiA2a2afj?$QRE$}SWZCl43#_f0}>$_#q31AEO-Go z1xrW3ywhu{F9IHpT$eV&nLpYh98Zd>Pf8R zp#E7==oMdcbbf;5gr+yQ$gj~x9$c>Tb_yXu7@P+{r%9l%$H>%&5IfHt?IoA!en;@F z9@Wk)C7IzjjY{;~)JYY*ab}GCw z&!4taw_W=!`LsiF_*J8ZlKBbgVo{OFY)|f>&5S%K#tO&(LUxmUy9wx@gY$ZzJh*^` z%LI#Tl#C~d&?~PWHxN*#8EE|45<{u;9`aDbNqho0R|&T<4%1+dsemA<;Q2)Jpo+ zkDsX}FI&7zQdM%4$O$}Km~-rudv_eD{U*WM5Em5ZqHhb}I>TDK;OlF}h27%AFd}#X zfR%bvwey&}9w2Yjw3YkuGL~h?%C-Q`fA4K77;C3<-Q1;YrNKF=aUn7`+~|jd&$IT> zun%LmAEPY!8{4TRX0OfesHyYfj8l(~qI=8kRLR7*&0e&$giMJ=NAP{GhG6K++Eunq z5E^L_rD-b4RS8%VK6}!5SlRjB@_rc^k_iWczp85_Z?5(cr_;ZV2g6ZC! zfdNxylBDS)Evsjw`p6rcUqS*g5)CQ+4?@R!ABD z?C7>Q*@N~om4nkWm(`nBA5KEVgf>Dg#I(#QHC4Xd`^kDqVK3+R``FccKJD4kYl9{j z7|+!|nLz$Ps7ZXzGH=2&<)Jn+b?IuW>bOFs*V3JJqT>$LL4ALoNtUa6wjeipuS0l< zy-_pay6h(gdhMXV>@^cQ!-t<8YFFL(R zs3Sbw^v;EHUt5vbnm=`XW#R*|bU>n{;mbq8o{rf4%~B1o{S+7L-NsE7bEJ+^3|i9h z(=e2B>Ae#8T=kARmrR^mV@;yC2C}g$5AaUS{-uiE-d97D{y`gG)~uebZxaXC2zM%5 z2JMc@84C<;ug4l(IZ;B%>+!I~2ac<-yoI9bc=CJ{5$GI_LJA^9)|qsn~{z`NF9qjfD z--k>lb855Mi|WDYklMw8x%5T-x?x~1*op5IzxQ3=p@sF8R$vGhRQtJb$d#c?##C~L z^~Hg2T{t_~G{Kie#Q3TcX=tqpvy*v#RbT=bMUq2;A-;tglGixry@Zo}3jsM>p>&+U>M+Fn63kaB8s*a*igzIg$&4HKaF;Gk zb6CSm_Vsp zO62lr$O^wrCXnI5?pC}089mtRV1vF&z0JO75P1R<5sgyavWYsgx6q&>U7epzT zi`qJU3>xag52bJf>4=2H?1umN%-vO`k9vPKKIcSU3cxwPpI@}kh~-`^w7>JqGCTu% z;f|_QEZ!(zMaOVOm{1P2n%~9mgeg`=`U=R-t|;EKMK zvtO>*AteOprVJSdBcglBj?j?5j-x`gwEvK>(2SN^{`{yFO70SIk||HLe)5$x--KHy zIV>}dyeM`Zi8tiR|5PEavO2b=T9NuGXH`ni!uGzJ13=}h2*%AT;ebhDZ0O_XF3@4vPDyUvhnf*q)aYZ-0Uim-{ka5?AZ5+ZJ-xO!*OvvykJU%-Qf28 zy}Ie1nLIeBX$3TN`N&Gq;Vt^$M29skG5v_TUzWb70**%Vwy0{W4U3_HRFo^HZ#3sh zmN&Hie1mo>YqXX)miw~(ob>n28+UjohL{-yx4jM}5rG3mP_G;fzEj`KReV`y!BEEjN1vnHdhw&(MM(DUScflr*7Ycz)a!|*Y{0}vN&Op48+D?Dx%f{ zJ=iDg3Fk!Q>0fn^8&0@cb(L4v=HiA6PlBj zv`*j1zu!Mi`y_EM`+0;4y7PkmoAZ@51vA`&+lpUJfMFOUks^4G5TRWac9(;Lfp3Mm zQ#UOc+b;22J<{D*4^S^xOBA-g)pM?zXN*45?xthhkyn`F@HFIBqy5%7KpbxH&H`Mf zjgqBUUFNsXOBo|fvx*$l@cZPw{bS`r{$jw~=M(Q?{s(x^`8VFL*sZgn`O;7OBh8ze z4TvV23?MYsQ*M)qo%;lkSn>!psqV@Q!Grv2S)KZsc9)oFcAY4b?YNVMJlMw5o_2Vp zFh8b#TEU{`xr8a}Yqg7+YWd4`)kM_S*Q&b%WrQ_GkK8piSvDsxlikz4o9p#HE$v)V z-|XF80RpC6+N&MBH6keUEv-k(z7qjDlUt;!n4@xm&L zi$+Tzf=#aA{;_EWwyTohuxP@lX&NpeY#T#gk*Wk&iG6K5k9xJ&z4wwH05gZ+#msdE zTeRf67?d;HZ^qE$HLqgaM(zIEQZuK9T=$grzG$pdWz_Hv#UAE`wA$hXgq&jlyKZL} z1)J``qsUYVS!GBQ~XqhBuRE zxnILVMC1-|@`$v*G?PgXjfvkV9e6K%2`E09T}^xBipHhJlZePxF8guVW*IJ#)b(fC zIRa}$Wyy{4M5=-lXD^iB)tGziQRz%+%29U$zY!nlly7CeFj&Ey2-$fIht#bcFMbWR z4I%SPEXEH}{X0YXoG9qmtM>rSha08MoSeK2%sbqdv!YM4n!OfiHF5J==>z z{_1kEhf2jESM>6$)c!KD6rWc4xGNOK??TwX8?@hrxU-Xa1wxoWyto6_-3TNjh1#}+ zZ@D@o+C|_y_6y<|MFA`23tsE4A}=!dj3on@<2Ugkg-CcaHwlEym^)D7^;6g$MXz_U zWpBgtowSKge3_)r8M@gT0A{*7NPy#sk&W=(hgIyVYOF=9$JDsrCf;?fC*cpg>-q^t z!YinQRzwdl=XFS)K+X$Lf**K~z=Uk#;E2Fkx8rd=4izb-TkNlSemhMR_Aw}JAAp~^ zrZt{`I=;RZNF9;#7_y`a};6CGUKbi6NT*A@B1WXS>Sxx*Zr|F<;sj0EH62paD~UQxr`tR9H9#)?ug7u zJjiz_Pv6>h_F1-Wmf=comvq_UJbMd2ij{6=R`k`x(6sB+RJ*iCp3I}#S$BeZ2oX>N zgN=L6XXk(`rGW`tjsh={m-o;&V=#Yo`kmw6Dvm8Xl095qrc8(7G(gMhv({K1x;IcSu zb4Y-1NR;Mx+I+V-i}boS7g7MGo%CyFHQMGSHTC57Q4MXmrOY&JEQ znzcQAANOhM8&$QDuD^FSQ&Kv8(?K{$= zny>wAvPSbwHg}Hv{HFdLS}B~;t3Jw&VsKI%m1RI5udXZiNq`G9TYPigGR2O|cCrV2 z5!eXiv78zz&h8L&Y5uV3TTxXD_-aE#&B#d0c%pN9Bsym;OKAYARMll_HY2Aspy$?Z zM%S_V_52~y+LX9CM$5(cjUlpp$*humsOx-bE_u@JtKJxLL&5n0S3paeyl~AoR4qQ5 zgRli3-{%0EZaKnuHgeD*bum+wAm`zoWn~UwC6k(DLd>pop4BU-8vjuyZl^i8LO3(1 zEMzgoNYLb!*t}Iej5UO|gbrG!F;3;DH$@qq#t~$IYsM|WUVWFw{xpFMbqPofHm**J zp5HJmJ~_a3*8tKG(o7JGb7WFzmlE^s$xH_5i6@2{#`w=RA&`Pgy45$~T2@-1FOK*Q z9VuA7$;K@NZC^}c#oBfXGw~XD8U)S?0L#*(mxd})E^7Bi#1H7o^f^A1>>NOfikj_J zf|nSet-Cxmqi;dvS~2Lw)0@+J3^i1*3Txj*Avl7mx)fkyf((rV-`0A$QLo&s1Uf1# zCKqj012|tp5{Yk_QNZLPB{I#RI#Q#AroK?djB`YG(MX_NKK~@jUuaHEn~n(ukZ!Eu zv+;)#H{=Cjh<(8Ug^Yckm8`I-T*J8X_$>j3?)w7yQ!fy%PV zzu2mi_T~~Y8Ha9m9vXH36XzNlXKcY8jjsmgS|tP;{Y>tflfLQ6sEpHw92-U#w+fP6 ztXq=l7q}vdd47a)7LWfmx`NF+KN57cn(_)f2eySw3tLZOjSDl9yqsYBHRkm-zW3Iy{DeM_?y{aps5Q&lrTKk!h6h!0q*t zJM6dFvUCME0bk0U)9dqnyso89eeeppEB4<-v6$*kh+T*%3Y;y+DVP|xW`Fq>))(DM z>5Lz}H-czPk+ObF<1>!0!4t!+`m8`??|<%}VsaR1&;JA}bEN+PQ2pIOtD*l1 zR7n1Wb)Thzsc5pqM2kW>;-9mB6`eOa{@T-sE85`}1@x}9HhfI6l8~4AXAw1x2TZOwwLq#aJQtt z*)&%(J~gY`m9 zQ^>k!O9!fOB&GRBq$NB(1mnAg{oTN-at}`Fu}cS93Jx*%keeAuIQPAEIjck8$jjiF z8>@EC7HL-Xu6I$jBj{Sx;r7`yu=)Ol zIzNDPIKl`G**v!Fl5s%d7jY^WnbXg8cwr}A6A&K)ar_WgFg_Clks|A<%rj;4d3d4{ zpbOy6gd;^&;|`n=a@AOwLS%4-hQrP83sEOOZEr3`bDz`Y&Ipuu+YsC?6M?+bj~`)l z(l`-FZ3uTaUAR-$>VCRQy^=-)Ro_^c-KgZRX3=uD#djp0hN-<3M_{&IyzNt!1#B7$(IIY?}UPgpdnKwD<4~7JX|S98)EZWtSjePiL_v zRu|VU64#(fMa1&a@|>&hJS_j^UXj^3Tk#;#yUf!&;_3qd8HG=DH6+Y}fVvftjwv=d zrtXTMy6*c;V%CXs(b>Q5qv`v7mvPU9pOU3?E9kLBkmT;mbaOmvC~-kFxCM$ZX;eSQ z_1lQ6CYny1dLO>K0?%Hgk`8D7zC5$%+)U; z@@`6)`Zr;F8|g-3i6Z=tc;Ae=6o=UEh_i7hO6WHSvhbOKYla*0Z^mJKZYT#mJUgjM zcb{8oa$qDjB-+L05=!GQJo6vK$}mk6ocG?_Us@qsU!{QaAO{d^yaNx|R9O$jrVSay z$e{6-Wbs;gMCcJYd|-qoF9vl8ji=TvNKU5Cd$UMUIM4!17-70NRjCjIEdsj+vesEL z8IQw^iE8eghs`-)36eAr|&EBYb?<~ZrXro zDs80(S(%m4MAK(pOTP6%>=W-p^9jmfUF;8AN($UaDpKyRMx!tdjjhEA)z!wcize1Q zR@T`K)kd>w$u^kEfY+4zm0NAU&qe&#!V;gsGyv}NU$L0k{);C4@5}pN7xqlc+HP+I z`8OevJ5hQbu=p9Ie%}b}bu)G^w{=w)*(#@_5NEfTTJ6q2q2N;Jl_KM+NBBakQ}DMmPwEJ zJdCkYl+~9e^;P?VtZ}I*1-3#6#^Wu5*g-A#E&ZtAo!#2&JO6=7?m7sX_fJ5>^`23b zrx{h)%f3FHem){%cp$=K%ZG|oeJ0Qser3`mA=GkXKvu1V8`lV$YCiZNDy>wtIGccRPIk0oDB>{Z?Iy~8luL`z zp=@csWKCdSiwTD!2YrHr3^}#x8zIQX5oJU>PRp7Ri}6*o+Y9#2MvRWJAZJ(v>#RY0KzUhJ7QPwn9n~zIb&EYkczYFcm4mlPj{h@N253Q)mN@+w z0<{?Hxr!SR1={C~37ZJ7YQUc=x508LbdcI_v*dN%Oq7&ywQ%^-G$Yl3r)a(zpj4;f zeYYogfvhAq`MEwl&UYyXXRzPFzzOAz|7la!>lcH9QqE-d)Fd{!G#UdZ8kzgCI{8MI z6iqYZhYrBhakeXsE8 zIZX;Nil^%GIH_d{o%EBsHqaH5@xzW7iV;fD{tYN_Uiwt&SgBUOgE%>xtjl)9BNli9 zJ-9f?!2L6{LUQVLca5mQGm(=J31T9Zi>s%*(xfZXeaJ z5_FWlmOG2VTi>^!G0d7L4{RWvb4p7K?R1XHTgFfg-{FEhc3(&<=6YV=jTG7)p)&*X ztPde4)qV{P`6ahz8GDn)^W6=3sE%9WiLVq$uyOB_SbB%LgAx`uu`O4e>8hX?Vf7uN zSb{$dHM~9)$(WNJCRB{=?rL+&6~R65K)xae>XG;`kM_~pI#5`3m_N-y3$eo7`#Gih zGQUE)oqTHCZ0FwZIIwT4lakmTTh!W^mJ2O&K?~JjQX4_P+hqA^`sozi3#=S>HCn@q zmxG)Wkz`R)?ynIjZK6P)cRyDP4EWN*Rb?ua6UKe9CQwYv^l4gGhSD`_LtX{%JyhoY zK>SzTuo{UxgtS=ZTnD2_>I`WUW4&(sZH*; z#|sD2nrlJ?wbUi?nY<3}3+2{}R{;@5W?#P8h8mlx3Y(#bgDrf3Bu6b(CA(s9@cvdY zRaCH`B&S%zoiAuW)QC#DERCeAqnyua&saOI0IuN){vLBcN~~z07{yK(-`m|%``ui# zkwX{+0vs+T3pRyuuQ($9Ex*jC3^S*o3}m77ke3)8vgog5%!IsGYp9NoAwx;%YFSG{ zJng`rZzHmhyh^5Y91rpAb|Hs+x8IE{6Ik1xL)bp3B%O%#{x$!#GcAf?h$8Dbl+&=6 zV&V;ep75}j#lb5c-btU8v-uTN{W+Uc5lG1if`)4|O#}<#At#u|0bcz+G8|4eRDz?I zT=ojd;f+F0oI-fV`r|;E0wwcZ1<@Lq&dz`n}*p3&~@bBVpXQ_ zSBj;^I+3+;R?b!TenmgRE8?=wDuRgPW^LEhS1XpCG(EkoEAJ3U=W8}c?c9cYq}nUT z>`3p@O%YEXvx$Ed8GLuK#uw&uQOQul))IZU+t|PsRn@R7Qhrl>W~zKa$Y1(8(bfyw zallIBB_~uSZip4P=uwrfkZ&y;fTlQ&$)bqa{208VIHt^GX`(#`DGbWL;EF$Mf7> z8v^R-toBK&A+u*I@L^V9j;o<+nri}Q3HdEZu2jBO13%=awm2)W4^MoNEP0K#YKdgy zZHrV>=btS7EELG4efhoq1!QDhvAfB_JQeUi>=R|p*uHmBJAyD}&1E@k7uvwS-6kXq zw^VkBu+wN4(?|*<;=79ETTI?9zewZ9S<*}8sWVv)3OnoJzdldv?kFC+(@W#wVpZ;x zg8bUlBy$hOj$;Ij>odkfu_e$@ZqAWQ-Y{c~nDpv00`^S6{DRBk;^46S{+Evu%Yt%d z0N{3q5bC2xiC=iv{cR=sPum@O_(rs_>{Y|oy!>(Ogd|^|T0cpF1UXjS+gN?&A|{q{ zggU7T+5}G>!d-unl1XMV-&j{Du1i)6+}*ATUQo&**)HsPd!=LH!Tu`J6u05^4mWL* zO8-F7=73{h)7^0u&t~5Z)f}s-F+g~ZD9hHCLf@4k>1 zl;q0n#%TRKk{rg?X?#^Szd5+udHgmniBOHMPP|eYdd0>9R8#egOHOxL7I&H!nuKV4fG_*VsL6;1DZqS4}BO4zj49qUOHkCJ_jzY6E zGBXl|UoE3`bfom1P*LX%@s3~4YJ^)f=6dyJVMw9rl>zkvaP_&Z6OF|XJl&lYa$IgH zG}|>MoZOD@AVmd>EhW7wEqT*@f(luO1mTsCt6<28cS3-c8nii+`Q&;E&mK&WS_5jn z*Z^KHze+vKrdKbwk42JTe`m1xi(^@+dFwONMI>F&?2JjA3kJL=R8P_~5UVYYIJByv zj+u;(i&WeZX%7=7rt`gX58Rrcxzfz42iS8r2 z?HZ>+T^%Ge=Xx4aW6qV#{ib9A!#LJKs08`ss4h-@*){6M>|U7my+ zNfjh~$}LbLs32_3c?#C5-QoNbfpF@}HG4A%cAYvdGl&~(RoaJohjf$13IV{QRKew6_ar<&BqR( zhK%55xPEMMyS^H-Txh(z2Hnp{+A{UbuiX#(3IiGu^94cLOW8+hm7`nga~Nt8latdD zyOeJB2h8%BuihXo_H~=5x<&u zd5S0l1g^3>`CmVtn)7opcl4NuQmXoXZX~0e5FRdWAm!IIbxr2&5fu-aAvc|C0XY__-#k1_K5^fObUSmI z9Gi#>Zd~4LJs(t?(J^o7%WqJhIw*z-GB1d)pfw&)t@?CPSgo0Qe=}NOF+aHm0Sr@7 z@h%*=m*d|MSC+&jsadEjaLqhls&K1nJuICUuslHxahqnxR=HAVvB^?lvS;1EZos2i zl<%`vKgCiQ>x8O7fFN9%DfhtJpr*8g0{MTUBV}RDQZ)DoxW{ zecZmp0v!)NVc8#jR!qrc)CoHsS`p>U+HaV(d6c}G)22Sg^U;3Gqc1nqIjY1A$C7F~! zt`OF(NbSQfeUV{uP&>6LGa}3Pzl}oXOVS1fk>LT%!TZ3sfg|$;l@g(>LM?sJ*z-Sm z$pF@8xh6rmGk9vF({|%SXWdO7lIv9+s^=y!2n_`ObXCt zi?H?~79O6%2I!xR;nF7$FAN{S1U1$zc3jV!)PRbwa4tH~GkWFCSn$Y-Z)1nqv`LL} z2s-O|&+6CD?nqd?F@3UCzrQBCZ)yPf;SQVG4>Ti(Q&$;oo)*e#jU&Hcxq+glqgPzT z(un^3r~bRA*|C^=i;Fo`tyFf1cwA_!_XutbAbe#DwfV*^6ZLfC!gp}lAjj@}4n6SX zV#}bLUEt&>Fo5%@z?azBFukcUkn%NLLCIbqA0Rzd7bwpnm|v3VLJ#w`1O?V%_cb;2 zMPsPNwkMh^yZZ^>U*ncc#HOm#Vl6EJdtgBp*M`jdqWH(*h@v z&+V(sU@I3{fDam}1+TL#(%cs_24{=Z2H^2Dr_HV{yvgQHKNH8xK{IC`k&HH`vERqD z9QEtZV3*t1SI!xXV26MiL~h1;a%J_tgQ_8EA33{KS^of3 zk@AvEVCB5rlAS?9xmWp2F^E2?i!n0n4UZC!s0gt#U3FGuucGh@PyX_G!UPoVh5~Vf+$)NVw6)dvUN=b!y)*{?B{4B`i%MJ zf8mEOV+iD2H4Z`LBiFXkG;%>OE-2)xYIM6hy`WF!yS4S%2Kp`!GYid~3h(PFqh3)B z@~^)D)k+a0rdWcHR0fdrE~7&z+2)=s>h6E+S@}GAg!J03o(V}{h+wNcdmB80ls;S= zU5W|;oddhTTGQl0UiB?oMcpiJILmstU;mq2XO193!HS(xfp0gOz`5q1wPXMOoc!C6 zA^(f#+#S)DLBq#sj{-; zUt03K8{r2-XgH04A+OjX8m0`}!vxy-%&IqF^c z$yyn93j^xL6g=gjS*uehxt+5Tp{*ULi>^ws{L?U-P|;;hn2;BHVLg7KpKL$Asl`(4 zg+gycr5Lb(Pzv(0*}xtShdT+K?jdQUR1YL2S$fe`v>Bn==#*ZM znNh~iO35~snmsG4#a(2~K}c8x!83G$J6Gv}kY>aV)n_Q)q*GvkAbUF{tIq2gGu^74S(_SBMNdhuVy6eGxT7pymz7 zwq}YUxOi%0ZySj^P2=`-fRE2@>*jVQqJ+@X zwCgSR-5R}S!_CUjy(~R~$^wt`c1o5*%0_Q|7Q+nA0|{C(ix zf|@?GYx{|6WF+~4Qi%2_eF4M4+1!B>jDnbK%OS9|6~$s*T)R~FJ$%u=5v}AknK1>} zXB2h~Rh_hx7>KQI<%ZddIdLX*L`&D5nQaM(hdRM&=PAvDCpG&ro4aU(z6}0Kghy8{ zlvc+PnsK*QL^v6SHENJx`4qV{aWxVjXZRh=UL^KS*(VfmPrRz>y1i5E%DjUWT{IY1 zFqtIr*9goS!nVOWG`i@c+%{M}-NMIOEg}Ie{@_x92~36zoiC^KEDrr&T;OKm)XiLM zlCnmrMdu;Zyv6P#yEelWq)Jidh#~LQEeWFbqp~@FUlFafjws`P2qq3Y=boh1HdnGR zQ%#I84JOF*-gYLgxqmhSj`PJ-PJN#)S%(8hsj!G8A#QrZTSg&DqtH^@oe+c}gMb0% z5k<1@r(9D8Nd?`Z^5asUyi~+6*f6_Lwf%NM5S2kv8#;uSmD9+D>qSq_@^F~I*tc~| z>qmyiM6>-0Bupu-1=>5WU@P}Ux}j;tvCf^;t=c>-%ewg2_qnehE1jiDH z;SvPE~-=p-MgdBwtO}w0=a0mzz1hz3qSo9@*cSexnj5Q2JzaSeUu4Q?+2SL0YrA z7@BIjnj|WzpIoLo(x@0xQ{ZLE?i-~F+Gi@_?j&%x<)^Utl8CZcD~<6?K32Ywgfx8F z$212iwYhIYjp^zPlr>bXc?hKN$ZeJkqPzXI!2`S252>$fKh%t+<-RVJ$eg}HZd4G{ zBV+s0zRN-RX=9uBBTl+<*N0|4$uFUXX;L3w`x_ja(E;~7O;7fd9Ygr!o|IINA9C*SpO;YdAkB8L+$qvL)RtCZSEsboKeFG?i1 zg0>MfcqsuMaGWHs-^_?&V?F?kW3f0`_&KULPIcI6C)!*_&=5r>h3Zl-rPbb5g0dQM zz|L0Qz_TVHQ<`I|L27Oi&YXO}2Tbro;`z==b8LOSpz)Z5Gx3nluef>9Q{Wma+jlVWN`0Y35z3=-`gaHj7_bgF z1PlZO3_1cF0{XvQ?wbK9(11t~02l}<02BoT3Mo1`V z7+5&q2P%;Ppdesipx|KNTMhiQJMixSa1;ns62=#hXfoPRq}J$6-cjk$WG_owFl2`h z$(eO*d|+TPv9NJ)DV|VLQ9osQ&dSEl!6_gpBrGB-CNB3zUO`bwSw&Y*-@wqw*u>V( z-oeqy*~Qn-KOitDI3zmeU2I%@!u!OG%&hF3+>d$rW#tu>Rn?zsYFpddJ370%dwNGk z$Hpfnr>4IyEw8Mut#52@Z66(Igi*P{1Kb7$H$#$UtdZqmeRsL!-ZpN-u4JA!C+3#L%%BhQ%ajS)w@lF4_;t{+eJu z|6h{*L$C+Az5?KZ>E1w5z)%2ufLpnY5Gufb&T(0`(5b;ae21{(i4pj=*gh(AAYW6% z$>OSa*qpqqqV|IZ6(Ln-EXA?*rSfW|C72a)BH&6gY05#@h^F%+m%9fhA95R_~1Mg{iBY zdfjSORpt7Q2#-;QIMEinXPbUh7S4*WAUkgqp_Y+CB9=T+r>0DJF($Nz51vEYeLlOL z=$za`LoU?^pSKdWA)8yfUB@@S%uA%~Xg|WNu|^0Cs0^;>v2?Y~6set zvEpbIYJCTO+6mrh>^I%tTDv&+Av0NH36&bk^jdw}6!TY1oHC)(OonQ8$UKs<(NYDcqAR*av4tY~s5NyKEI8<{ycT{L3KT?QFn=1+^UBMaYAoDS#>T(g*p(}-v<`P0g z_jHbXZK`@>H*Co*c!yUxTKOs!UBunqkS-vqolaYn3>7baj`#&wL9?vHY*NrWuNfaN z7aolDdjLq|6H`76H`LnB%B7?x#98+Yr}#lrTl6K$I0ju5DH?=yfcdq}^h}hRNLv%3 znp54fWP|=kH<5`V%R)C1S6$1c0#i6fgY(0hsW39-isX;vsq92Asg4ojdG10^B!KY@ zBTBYKL`N#ZG^LNopPXtK$z;LU>zIEgbw4gNUN09U4_ ze{zK6Q~B|E31&l4Y<56OTyeD`({zswAu*k1pI1;a7!3_QM6ygyE6j@UuNKpiX94=Ae`ewpw;C+NavT5@zKNr4mL|*4^z7&bUO$^+$}Y}*hfIs zd(cb3QfGws>et8>Klc%;P!ksh&3L_G0mmg!2d9phd(E$8T)r?DfoQ78i#A$GmNow^ z1I6-Olv3Fb^+SLRvj~1o*qknjc(uHO7RdHH#djtP6|AY*CRe9KhoPnyxKe>N)o-&>nrT|?LnWAk(|_{JR}fU{zIL|`NRx6qhxF8e3^Z(D6O zRpyFT9ZnEl&XFW?_!(t~I45OZ8p7AvAjOHFY+NC=dE$Kgy0AF zt}buG>WRrs+oP;uZ|zMK^XQqKZ8f{e61LH|Qz4K=eSkTS0{;IJtO6v^pqk{bdm>{!p={NUl{AIp?Ur zG}yjqXW;3#L}ol_x(sQL_D^)44(0)5ayO>rh0{9F*^5oSoabRnB>^f=azAbjL3y1$ zkDf4n6Jx(u=;A(}?4f0WDtGR$1EPP4h{WwE>!xrRRy&KAio7)uW-(ZnbwO3=uaANs zdUR3-Z{PQk7!|@^raMMBTDXtg0*r>boA{LY3)Ij83ZKE8R9DLL345AgHy(G0^@7~( zIF*zly$jBOSc2;5w<;>Dd%*;0jwk58?~~DWPZZ6Un!uduT0E%6mq(r>;M`E>@1UBZ z;&tnr_SsS^eD((YB!aj!tw(Nl!XR^gvQ;4s75eoCXR>Jtr{QpXFf$i{i5|Oc`7PYX zXeo}EYqRa>wx>kzP*SOt#fIjI?eZrVz;7gY-UIEs}K33X$5wPOq&q ztt*hnnVMOR)>jsTKxv-puyJn;0UW#AE*4D`E%61baBfF--aQ-d%tg;36{-y~*PUwP zV;m^uAetwP-9eY(hxiKzvc`1BN97??b^{-$uhgKWvho3l(8S?L(za?1U9EgjvqmME z_W-T0Em`#gR%S)j0?-a#&FxQ-0gTZM>^*Z~vy+-a0GlhmHa>(++(MPufRu@gQc}UP zqW0WX(SDOG%ogcFCo-Hid%y&1(ji`pieuoYm^JF&oOk8yF$3UhHEnAFE_zpq0UL{+K9+Q8 zpW_7=o+0;ui|ok(g3dF>n?VklVf}_Qm?snWKc*q_^7*Bpg&&)yXRRA&y9UFliOXj@ zql&~ByIC59F-`SAXR-qn2LTeZKqY zKj$f)x($mc1U`jV0S{oz>>s|zcG@~-`f_&8X8N|@osHr9#SncMu=$r7f*pJ6=bHZ?aLr@k@U|z3Cf#$X9hB}<~8tHs^45k zW68ZY2!fYmAjg7JTG;Y^h%+jhbNkkUj77oJTv$4--%O0x+M?}vSrn4~gNK9%~{1(fgvLN?; z_nv56z9P&`I-g{=9vLy=*rjDYSs!qv?^)Nb2q1VPxToHx=tyDM3qxPIgL_wSd$+`F zn9wZ7FrODdgmE0WckP%YO;q7>iGzc}UYNe*E}l*Yi{4P?<;Y7VQr-}Oi5gosu0HUtz#;7O||B2v1yTF(LEe(d4G8foa5ld_*OXSECljtB@<&^ z7IFf63Xx&lCDzohlWAw_k(q`8&m+@M=ka@fo`0Oj!VwwE8DeC>;)U2X66OIo18aB@ zdn^II@d#l~{w+v%CrlzEwCGIUwkMJ8$kRENyu@=mV!JL!tE41K9txxpXZ>~DNDRo} zWLM^om(sr1bv$am?NE$JI!xt-El67}1Lr5XVMT>88iOzV60Qa)O26#V$oT3)SH0}M z5Xtk`#G%=6V#UoB3Q~3C_eG010_90Z*A1wUX8ycd7qOaT{ib0xhi?m-9n0EAh5YVg zp=Xf+pX^?3gkzSunJlk=39p($Pfs2$wg8Ltx;a zK~S+e2uqHJEU)kusL!)bHqyPOH1g1m(znn_It_zJ;H*IA= z4>*9jMxwYdzhV>=SJk%FkiecQEv<_rd|oIfZc}l#KwqR1>s;Em%Du6a%+_P@p|F7m zo2H^y4Bf9CtizsS>Cu?7u>xL1q(Y1ICWm(rG zB&&iX#PilvBl{u4rHemL2s7D^J^iYX zJ(9G*DFoD%YMrL~nG=QGW`lzfwn)6aut=In4M@OO^z6uaHJXOG7XIXe>(8bJ)>|3| zv}rE9vl*U!?t$J7n?zMXkUHE3x$nYVK5|#9yYw%sUB1>>U-Ln?#_sY+_$H<#JjxP2 zntNE6x|?%P`Yspb&N4Y`X|>l06TPjyny>I1&`IE*?+gh@1S+h+5$Op0EAr2yQrpVv z{{|&+H2$q5zmc*0%7FYM8R}XCHm4t_o-7Hrj;wMN^j`UnT9CZ1YgM0ClSjN?mUM=A zC{|7t9*@=+Flzx6&OM;LwMM5?DHL!%!R?X&nQ=CWmIBh>En=drF^RbiU;&WWegPW)WU z{^)~FF%ZW}=ftm0pk_xz39xx6GH@14opAe`bnuucc>NbFPgW>v4{C~y zROv1jSYC5(yEsjYDnQZbqWBBl9$7Fv>2PaJHM)MMw$7I4p3tlGQF=cDj(6S#foI(SUmv zWL2J6%-)0>kC^3HtB9OxCJz(RcSlAu9SkO_eApa}s(oi5a0o7uSK}q-Iyv0@?q@?W zX0f(V)cX;%^K;tx$B4&Cw;vE;z-B#0{4m~njCh>6_yO@2*yoQCKkPyu zBOWJyeLzG2eW)HIei&6gMm)~u_JGI&COP{F@ss_{gDu))+T+ZV542Rw-_aiIS02;; zoXPQl1ps^iZVY{I+aHa3e7=7nC;uAv!+`a%u#aQYKj6}VZY%#7ala7u zhb8D^+~dgB54bXhe~tTLGV~btIL7V+&XMI`<9^sCKE^!`;rM_vW?B|26K9AY+etkE8!Q@cIP*AG{xz?PK2KU>gs-Lb0ECe~%6E)12}+^u}ZI<2Vx! zWVlzqAU}>e@fi2m*Yp9WAommQ7lZJZazD0Ld%(pi{Q~!s+<*Ls-_L=M{h1!fNy@(< zKlW~VEcfplO0Dq=+&?w+_cP+}$@H3kh5Y;3@%QBCT7QN7U`GGfdxJ~+uaF;1)Big8 zh0b3g|Na*H``!@I{VU`L6Y9U-W(mE&LVhqy{#Rt$hY<{XC8z&a$bU>;zr3yg+#P>L zs~G$x^zSddpV7cO(@#a)@UNgBOi~~B$793J2ehHlALPeoo_`R2ZzS_TP`CW8$;{(s zJXRDxKvLZP){H-^jUNLZD`Ou3uI_&T{-m0HT>Y^Q@S(bZ=O5Moq!D~v{m*KshswYO ui`P$m@OP!uLlnGHY#`~m>iJRpJJfBPSmtfpE3 literal 0 HcmV?d00001 diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.rtf b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.rtf new file mode 100644 index 0000000000..3b841917b2 --- /dev/null +++ b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.rtf @@ -0,0 +1,239 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff46\deff0\stshfdbch45\stshfloch43\stshfhich43\stshfbi46\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f43\fbidi \froman\fcharset0\fprq2 Liberation Serif{\*\falt Times New Roman};} +{\f44\fbidi \fswiss\fcharset0\fprq2 Liberation Sans{\*\falt Arial};}{\f45\fbidi \froman\fcharset0\fprq0{\*\panose 00000000000000000000}AR PL SungtiL GB;}{\f46\fbidi \froman\fcharset0\fprq0{\*\panose 00000000000000000000}Lohit Hindi;} +{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1504\fbidi \froman\fcharset238\fprq2 Cambria Math CE;} +{\f1505\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f1507\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f1508\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f1511\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;} +{\f1512\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;} +{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;} +{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;} +{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);} +{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);} +{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f1164\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\f1165\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f1167\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f1168\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f1169\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\f1170\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f1171\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f1172\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255; +\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0; +\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;}{\*\defchp \fs24\lang1033\langfe2052\loch\af43\hich\af43\dbch\af45\langfenp2052 }{\*\defpap +\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 +\fs24\lang1033\langfe2052\loch\f43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv +\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 \fs24\lang1033\langfe2052\loch\f43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 \snext11 \ssemihidden \sunhideused +Normal Table;}{\*\cs15 \additive \sqformat \spriority0 Footnote Characters;}{\*\cs16 \additive \super \spriority0 Footnote Anchor;}{\*\cs17 \additive \super \spriority0 Endnote Anchor;}{\*\cs18 \additive \sqformat \spriority0 Endnote Characters;}{ +\s19\ql \li0\ri0\sb240\sa120\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs28\alang1081 \ltrch\fcs0 \fs28\lang1033\langfe2052\loch\f44\hich\af44\dbch\af45\cgrid\langnp1033\langfenp2052 +\sbasedon0 \snext20 \sqformat \spriority0 Heading;}{\s20\ql \li0\ri0\sa140\sl288\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 +\fs24\lang1033\langfe2052\loch\f43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 \sbasedon0 \snext20 \spriority0 Body Text;}{\s21\ql \li0\ri0\sa140\sl288\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 +\af46\afs24\alang1081 \ltrch\fcs0 \fs24\lang1033\langfe2052\loch\f43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 \sbasedon20 \snext21 \spriority0 List;}{ +\s22\ql \li0\ri0\sb120\sa120\widctlpar\noline\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ai\af46\afs24\alang1081 \ltrch\fcs0 \i\fs24\lang1033\langfe2052\loch\f43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 +\sbasedon0 \snext22 \sqformat \spriority0 caption;}{\s23\ql \li0\ri0\widctlpar\noline\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 +\fs24\lang1033\langfe2052\loch\f43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 \sbasedon0 \snext23 \sqformat \spriority0 Index;}{\s24\ql \fi-339\li339\ri0\widctlpar\noline\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin339\itap0 \rtlch\fcs1 +\af46\afs20\alang1081 \ltrch\fcs0 \fs20\lang1033\langfe2052\loch\f43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 \sbasedon0 \snext24 \spriority0 footnote text;}}{\*\rsidtbl \rsid6097384\rsid16590483\rsid16671749}{\mmathPr\mmathFont34\mbrkBin0 +\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\title A Text Extraction Test Document for DSpace}{\author Mark Wood}{\operator Tim Donohue}{\creatim\yr2022\mo3\dy30\hr13\min54} +{\revtim\yr2022\mo3\dy30\hr13\min54}{\version2}{\edmins0}{\nofpages1}{\nofwords75}{\nofchars433}{\nofcharsws507}{\vern43}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} +\paperw12240\paperh15840\margl1134\margr1134\margt1134\margb1134\gutter0\ltrsect +\deftab709\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1 +\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin450\dgvorigin0\dghshow1\dgvshow1 +\jexpand\viewkind5\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot6097384\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 +{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0{\*\ftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 +\fs24\lang1033\langfe2052\loch\af43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \chftnsep +\par }}{\*\ftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 +\fs24\lang1033\langfe2052\loch\af43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \chftnsepc +\par }}{\*\aftnsep \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 +\fs24\lang1033\langfe2052\loch\af43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \chftnsep +\par }}{\*\aftnsepc \ltrpar \pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 +\fs24\lang1033\langfe2052\loch\af43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \chftnsepc +\par }}\ltrpar \sectd \ltrsect\linex0\headery0\footery0\endnhere\sectunlocked1\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3 +\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar +\qc \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af46\afs24\alang1081 \ltrch\fcs0 \fs24\lang1033\langfe2052\loch\af43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af46\afs30 \ltrch\fcs0 +\fs30\insrsid16671749 \hich\af43\dbch\af45\loch\f43 A Text Extraction Test Document}{\rtlch\fcs1 \af46\afs30 \ltrch\fcs0 \fs30\insrsid6097384 +\par }{\rtlch\fcs1 \af46\afs20 \ltrch\fcs0 \fs20\insrsid16671749 \hich\af43\dbch\af45\loch\f43 for}{\rtlch\fcs1 \af46\afs20 \ltrch\fcs0 \fs20\insrsid6097384 +\par }{\rtlch\fcs1 \af46\afs30 \ltrch\fcs0 \fs30\insrsid16671749 \hich\af43\dbch\af45\loch\f43 DSpace}{\rtlch\fcs1 \af46\afs30 \ltrch\fcs0 \fs30\insrsid6097384 +\par +\par }\pard \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \hich\af43\dbch\af45\loch\f43 +This is a text. For the next sixty seconds this software will conduct a test of the DSpace text extraction facility. This is only a text.}{\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid6097384 +\par +\par }{\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \hich\af43\dbch\af45\loch\f43 This is a paragraph that followed the first that lived in the \hich\af43\dbch\af45\loch\f43 document that Jack built.}{\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid6097384 +\par +\par }{\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \hich\af43\dbch\af45\loch\f43 Lorem ipsum dolor sit amet. The quick brown fox jumped over the lazy dog. Yow! Are we having fun yet?}{\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid6097384 +\par +\par }{\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \hich\af43\dbch\af45\loch\f43 This has been a test of the DSpace text extraction system. In the event of actual content you would care what is written he\hich\af43\dbch\af45\loch\f43 re.}{\rtlch\fcs1 +\af46 \ltrch\fcs0 \cs16\super\insrsid16671749 \chftn {\footnote \ltrpar \pard\plain \ltrpar\s24\ql \fi-339\li339\ri0\widctlpar\noline\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin339\itap0 \rtlch\fcs1 \af46\afs20\alang1081 \ltrch\fcs0 +\fs20\lang1033\langfe2052\loch\af43\hich\af43\dbch\af45\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid16671749 \chftn \tab \hich\af43\dbch\af45\loch\f43 Tip o\hich\f43 \rquote \loch\f43 + the hat to the U.S. Emergency Broadcast System for the format that I have irreverently borrowed.}}}{\rtlch\fcs1 \af46 \ltrch\fcs0 \insrsid6097384 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b6f4679893070000c9200000160000007468656d652f7468656d652f +7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f2a24fcfda33b6b164873dd648a5eef2547789aad28cc56208de532e81c026e49085bd +ed21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c99e191c3061463074977eefd5afde7bf5de53d5ddcf5e26d4bbc05c1096f6fcfa9d9aefe174ce16248d +7afeb3d9a4d2f13d2151ba4094a5b8e76fb0f03fbbf7eb5fdd454732c609f6403e1547a8e7c752ae8eaa5531876124eeb0154ee1bb25e30992f0caa3ea82a34b +d09bd06aa3566b55134452df4b51026a1f2f97648ebd9952e9dfdb2a1f53784da5500373caa74a35b6243476715e5708b11143cabd0b447b3eccb3609733fc52 +fa1e4542c2173dbfa6fffceabdbb5574940b517940d6909be8bf5c2e17589c37f49c3c3a2b260d823068f50bfd1a40e53e6edc1eb7c6ad429f06a0f91c569a71 +b175b61bc320c71aa0ecd1a17bd41e35eb16ded0dfdce3dc0fd5c7c26b50a63fd8c34f2643b0a285d7a00c1feee1c3417730b2f56b50866fede1dbb5fe28685b +fa3528a6243ddf43d7c25673b85d6d0159327aec8477c360d26ee4ca4b144443115d6a8a254be5a1584bd00bc6270050408a24493db959e1259a43140f112567 +9c7827248a21f056286502866b8ddaa4d684ffea13e827ed5174849121ad780113b137a4f87862cec94af6fc07a0d537206f7ffef9cdeb1fdfbcfee9cd575fbd +79fdf77c6eadca923b466964cafdf2dd1ffef3cd6fbd7ffff0ed2f5fff319b7a172f4cfcbbbffdeedd3ffef93ef5b0e2d2146ffff4fdbb1fbf7ffbe7dfffebaf +5f3bb4f7393a33e1339260e13dc297de5396c0021dfcf119bf9ec42c46c494e8a791402952b338f48f656ca11f6d10450edc00db767cce21d5b880f7d72f2cc2 +d398af2571687c182716f094313a60dc6985876a2ec3ccb3751ab927e76b13f714a10bd7dc43945a5e1eaf579063894be530c616cd2714a5124538c5d253dfb1 +738c1dabfb8210cbaea764ce99604be97d41bc01224e93ccc899154da5d03149c02f1b1741f0b7659bd3e7de8051d7aa47f8c246c2de40d4417e86a965c6fb68 +2d51e252394309350d7e8264ec2239ddf0b9891b0b099e8e3065de78818570c93ce6b05ec3e90f21cdb8dd7e4a37898de4929cbb749e20c64ce4889d0f6394ac +5cd829496313fbb938871045de13265df05366ef10f50e7e40e941773f27d872f787b3c133c8b026a53240d4376beef0e57dccacf89d6ee8126157aae9f3c44a +b17d4e9cd131584756689f604cd1255a60ec3dfbdcc160c05696cd4bd20f62c82ac7d815580f901dabea3dc5027a25d5dcece7c91322ac909de2881de073bad9 +493c1b9426881fd2fc08bc6eda7c0ca52e7105c0633a3f37818f08f480102f4ea33c16a0c308ee835a9fc4c82a60ea5db8e375c32dff5d658fc1be7c61d1b8c2 +be04197c6d1948eca6cc7b6d3343d49aa00c9819822ec3956e41c4727f29a28aab165b3be596f6a62ddd00dd91d5f42424fd6007b4d3fb84ffbbde073a8cb77f +f9c6b10f3e4ebfe3566c25ab6b763a8792c9f14e7f7308b7dbd50c195f904fbfa919a175fa04431dd9cf58b73dcd6d4fe3ffdff73487f6f36d2773a8dfb8ed64 +7ce8306e3b99fc70e5e3743265f3027d8d3af0c80e7af4b14f72f0d46749289dca0dc527421ffc08f83db398c0a092d3279eb838055cc5f0a8ca1c4c60e1228e +b48cc799fc0d91f134462b381daafb4a492472d591f0564cc0a1911e76ea5678ba4e4ed9223becacd7d5c16656590592e5782d2cc6e1a04a66e856bb3cc02bd4 +6bb6913e68dd1250b2d721614c6693683a48b4b783ca48fa58178ce620a157f65158741d2c3a4afdd6557b2c805ae115f8c1edc1cff49e1f06200242701e07cd +f942f92973f5d6bbda991fd3d3878c69450034d8db08283ddd555c0f2e4fad2e0bb52b78da2261849b4d425b46377822869fc17974aad1abd0b8aeafbba54b2d +7aca147a3e08ad9246bbf33e1637f535c8ede6069a9a9982a6de65cf6f35430899395af5fc251c1ac363b282d811ea3717a211dcbccc25cf36fc4d32cb8a0b39 +4222ce0cae934e960d122231f728497abe5a7ee1069aea1ca2b9d51b90103e59725d482b9f1a3970baed64bc5ce2b934dd6e8c284b67af90e1b35ce1fc568bdf +1cac24d91adc3d8d1797de195df3a708422c6cd795011744c0dd413db3e682c0655891c8caf8db294c79da356fa3740c65e388ae62945714339967709dca0b3a +faadb081f196af190c6a98242f8467912ab0a651ad6a5a548d8cc3c1aafb6121653923699635d3ca2aaa6abab39835c3b60cecd8f26645de60b53531e434b3c2 +67a97b37e576b7b96ea74f28aa0418bcb09fa3ea5ea12018d4cac92c6a8af17e1a56393b1fb56bc776811fa07695226164fdd656ed8edd8a1ae19c0e066f54f9 +416e376a6168b9ed2bb5a5f5adb979b1cdce5e40f2184197bba6526857c2c92e47d0104d754f92a50dd8222f65be35e0c95b73d2f3bfac85fd60d80887955a27 +1c57826650ab74c27eb3d20fc3667d1cd66ba341e31514161927f530bbb19fc00506dde4f7f67a7cefee3ed9ded1dc99b3a4caf4dd7c5513d777f7f5c6e1bb7b +8f40d2f9b2d598749bdd41abd26df627956034e854bac3d6a0326a0ddba3c9681876ba9357be77a1c141bf390c5ae34ea5551f0e2b41aba6e877ba9576d068f4 +8376bf330efaaff23606569ea58fdc16605ecdebde7f010000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d65 +2f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d36 +3f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e +3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d985 +0528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000000000 +0000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000 +000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019020000 +7468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b6f4679893070000c92000001600000000000000 +000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027000000 +000000000000000000009d0a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000980b00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax376\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text; +\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2; +\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List; +\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1; +\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision; +\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1; +\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1; +\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; +\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2; +\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; +\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3; +\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4; +\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; +\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4; +\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5; +\lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}{\*\datastore 01050000 +02000000180000004d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000d0af +77916744d801feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.xls b/dspace-api/src/test/resources/org/dspace/app/mediafilter/test.xls new file mode 100644 index 0000000000000000000000000000000000000000..1ebc20bc3810a239e20fdb8d221b8830594bce07 GIT binary patch literal 23552 zcmeHPYiwM_6+U;pw%19B?Ywv=8|URXPKb9+c;vBxgqLt0g`m(96gKucwqm@ISwoPB zFm3r$C~X}YT9TGhKvfHU@MxhbEg&I~TGLhq#G?^~iXtj1D$-Uh2-tq#%+Bt8d$&-j zsQ0es-kEb}zH{cxnR936&fVYsruEf3??2%+aUJWVPChI)NrR1UA>L`yErR>SB9rF2 z3~`X6;}fKTrUV+YpR_&UUOkUEQ_h z(%5{vQC zJ|EuwB**>BF3R;??#$FCjdF4&(~6Z&9Ha@g1fsUNbGgt;Sx=J=Nr~*FC>=>7St+@r zT@iLHU0ouyEpLO|JKNXq1eX=E6h9QTm+MH{3w0zNB{Fzmo#|wzYvAOkM6Z(tVCnGvF@$u`ylB8BK&oPZ(kEqiBL#12s zJCy#t^zR4IzZXFNK>+CkX$+0Gw|I&})_F?f{(o1LzMa z`fN{o{UU(=g#h|b1L#^FFTR$?OaHOL!KlY&KiW>34gJ>E*4w7uHdW#WRsPm8y}$pl zd5_JL_``}`4Z{D)gx z=S-agJ>RbA(@lQhYdvfI*W-EUr)SP${PZ)+(4WH)Pu7+7FvHMo^|~%GORl4W%Evmn z^_ITP`xDD$zk|*UF*yeQzYZ}DBl(cTDnO&HsQ??LrUEqcnhLOg)l`5ErKSSx88sDP*Q==j`%+B>==^Fbu&mYsTx6(; z!}3}Stf;jB29dRiwAxXC{mc=bkc%u>rek4Kq8f zdUd-y=30oMJ0@#}wsP8lw-%=(n>HHO!ZVyP9kW=R@YLdg2OjVkjh$v{K^m~tVT{I3 z^A4F#LEcI&Xa^3}?oV9e>W^zsAYOK_o?1}vte)MeQSXLk`lHuxwV_nSZzJJtWCIfH zw>U7afOw>%j_V}XZLhU$V5*#2-d$Q2T7&FK^8hdknq)&`CJRiPeBi^n`=VuLdNQwu2mq&5#y6 zz9hABQF3|mtlr+-aG`5Fu}Q|My3%3A=N6B_>iSAqBuQD0aF+Dqmy=;Ba1|R0uwY}!KD=sAdDQ=jWUdgS;1Y-YIGq`hnwhp^GJ z6vd_?*Z>1NaIWz!MDhVz(`(i(I9um9mM{%mHs*_`TSvm%5Irwb)EiCq87 zZ~x7o&1^TDRbDo!5H@;PtCZH1>;K@-FZr{XLpI&vmNpM;x~s6XiJnI$UiW8ntecIG zr7Z|yW4E-4o;MzO*q_aDZZTUv+4xx6vJf_QOPlDKc>HC5 zHYd2*_*mL0A#CiHHqrC&D{uOiELJdTUruqR#jnX zsq|MLe8Hd1$!<12mbNg2jos2x>6Z`Q>d$7bn~jg9wTG~=TUsjpG5Zd__LYsX5(XN-63r3mX=Dt_t=;G*i6Ig zX^gwd&&{)3&&`vIje*Zfku5j;T!ZpKiKFzaoF)q@ps*-6=zJHb-HPz)W9ue=(1jIH z*pC}@kqgvrGa`q6_Gy36MHNt3iyL&Q3)F5QB4d5u_6J>D0flY2L6^Hg?e-vY;eAi} zgD$Cn!V=t|T`o|2>x=yJ;JJRF32DdERnE@#jVI1u};p$a${fl30%)b5v&k$ zbi_E95lb!Jg8c_r!{3k{>Kz@*76$XfUpSitA4eWvO(^NTpLoDxErjMcZX#b;a>S z(kV)P9hhu@O0L8&3sxe`+d0;NyzQ>M4cLWP$(xkR@FcjrTxR)L9Ay@}%EXHalo`c- ztr0c$?UifGC02|zqQnYUiN@kIPYEZl*>Va-;BwBsy+hgIY#~2#ZE{;~Utv6Po)ao| zYOzJm!S=Jgs_lnW`{yCZwb1!Ch_es&z{M-m`fTiyc{QPKSe`jApNFBYcH&wm6ytIp zl$LjyDp$k}7MK~V=@{>02628a+c!A8JBhi1OVGL&X!d*vMB5*PdG3aQdXAt>03BxD z1^YD|O|cP@X-9YHNO@M7jWk8uo7@U$HqKF|$5!v|dZ!2obQ zG>9qiZaX-9q01rE&GZ4MKh!POXjjXD*c3Sx4Jj84?;V9Hj5?bvHVgH+0GizkTV$hG zx_=St{-)9Ra^NF&{}v38;Fo#^hxg?AO8x&rmoUe{FZHN~-h)Q(b9jKdvNe`{k?}rU z;%y6a}p|&=#-H} zr;H>zMTdFjSUq&N&?!QFJsM&w>O4}O_)}#x)zviBZG(j&XsgRvs;(K$vklf!K*LdA zUTe1qu|`4U3$y^&+C$y(3W5sPtshrL!-i;s8wKXZ4SgWkX(^0l(M~3OLw0q4s z1&vLGr``?@Bj{JgYx%iVKE#!VZ%JG#x7Zd*zKq(n;977ji}XhzL9`sTs)A=6}m`yk-pSWQl#8MA7v=% z+Y`sh?KZizY3nyEcVS!hUAB8HcQZ`g!tV(yfgp17Tb8^p&tj{ysDO?zlE0=)UsC$Q zm$V>uqvQ5v3)!yD6hIkEraDt4V!9mbF2}k$xguA}$OTJA)14;PU5<5i8tha#mM+J- zJG-&iypjC!!^y%xE_qe1H$U8$yoy@F+PwW%VPJ5ibf<`|K?(U4UhBggqy=y5KZR=+ z*Kgz6o0x-uNa>hUy2j)n)OOHz=j=aOatO3THX7A@GiW#4XawM14!s+`#jzWHYHSv7 zs{`7Ug?Ldtgr+?PU4$+r27_r<@m8E6EhK;cyYKvN)2^+Vor>O}=*!gkvx+i?Jmy%0 zj*RartQ&CWi8y)9M);h{{dsj>jNJX|cq7A&wkF9i6hETjJ?dOljJr>X0G^?=ep^&`gTu;Ot8GSdMQZbWWCoNvjm)g4E>{ z%W>V6Ib--WE17hP<+y=|De`Sr)~Qo0hn0-dJ(i=k^gWj2rkNLze#*4R z6VOH0tFu1c1lzpH(JnD;z(!3Q6jKWoL9A|f;#k>j%LZfQ%(QM}b(Tzhy^Zl$J6idZ zW67+fv0!t>{OmupWa>b>TVu(V&63%6JeoP`N82^1UWHwPwY8eUQ0B?l$IxnbV=Vd# zLey&Et=8&oHM)T6`WiE8)8KWIR%4b=>hU3lN%VB?gMH)G8}B&!?v{a;d%u#9WlJ7> z2@gK`76N@I@2^Lo$2}i`o@g5a2O|Rrd~S~+&?nxEFcaZx2;}rS3i0(Z%i^Ol6=#0i z2WE=Kez$3`cO-vRzQ2$>XJ2n_NVneF)CXgIzqHol{@Cil;;xM^TJ*=!5osXOK%{|4 z1Ca(I4MZAL`S57NCS}uA`L_uh%^vsAksjj zfk*?91|kjo-)VsJcuto&FXsDr?wR5?3C^=Q&*xq!?t|e}nsax~|NuWpX&nH`iK8%Re$rP{v()wEx=FdIW?b$kVKe|(1yVO5MGGuB80^VOAy)- zmLhZ@@cst~yhg{zr-6&Gy_Gw5llWwT-w5+Fg<6g|Qm^)%Q6kz=GQqRRWeRN?&#d!b36F653&DL%b(wntImTUvOPX(R?{nOn z?fC=eJMXM{pEYaV`c!wmQ#^_Bb(kJ>WKAJc3G zUPz`8j;L)auDe)Ylcc_;-Z*v`MKSo*D14f@64zeKxzYB9yJm_35T>@8kI1!?OO{#b&VA)Du}Odl*z&G*ug z_aYK0UfQ-+ZU&iehKkB4%9k-7ekQ04(+IdV3$`%^AZlh~kl`c(Dml~{8=YGHL`&sl zck9L91(38Y+JHUU`wOc~No95_d$O~%gyNj=Ut0}=<>m77<5WavJ$B2e-)%7a9(^*M z@B<1*yz!A1cp(~0fZpHOYznhApTKm{cWbuCoD zAdI%HA;^xE1_g~{{;ju$dH+#^%C)F&ra=B?Kg_NDwBrDZK{)(iHYL~P& z;ysn{k5hl3ueAQ`$m!aUkmmgEs_83IB0X05jz&6>N=`kuNi%0~)@zJ+dKAW{I6VZB zM7pYT^B`^K-1s!T$;-m3urPwg_$x|1E@A~7)Prr?ycjm6&$=cfagPfPzs7$T2>eK* z$jmW?l8rOOg*4&cuG!=lEUB2(@TvwlGz+?}Z=kQTo`! z-b)I7U?$DX8-?TBI$j$JnUGD3)B+`07x*-Al5(R?qp?au4iRsewS(fls?>@^r6rG|Gt{zhRlA7 z#;?V1#KR`m(lGvMOSnk6<_`dk(zoGG?!;4CE1@{CM&VwTYxayb_IBx-;GuL4H57iMr8@>Ovi1Uoji6dan+7yDRbh6RMn0YWTqEz1-YU zopO2`5{vXNEXS$(j|ny$Tqe*y^UKw9T#b9{<Om(K!ipRNZoVO#x zD%8@>6NZ!CwzLm{v3xaF<|7&!{fy+g9;v|GFfG>}E6TQCk-8lAl-}(8h?ZmD z5HqA~D6vbGC0PbFxB)9mGQ8sQk*Y>RfTdG-?0XE2CU_Geavd*TC7+Cy^LFp84pB$? zRi+?6=#TAryA&$VViG=NzwRVafX~%!{1j~$HZoC~M&?brkZ%Z8lR}m7gIoE7-)69l zeCh^Y1OPyp@UD9NxhM7}h8BjbKkw{!KE183U^UB)^ATgpiOk;0g0_wj(IIrIG7T(8 z*l8I=Vw#eurko=@`oe>7X4xC2qfs{rrfG)_#5fGwWS>Q&83YDi;GRlJS3T;J8ATwR z7!MMs@N&OhSf+G1nehwG7Nem0ZZlh=RpuJpDM&iTYvSe`gLmfnQP3h|+lOUFfcmN~&CI%t@LY#*#X3l!A% zz&;Ly&RAn>Qd8B@_5z{p z3rE@HmV;YudM1p{0-_w4Nh0;+<~cZSgvon*nPbPYRtem6277%%ouZi`UoawVSSgP7 zNpK=*pK#pB!)*XU7~RE_xPLO}SU+pOk6>^xK<$1jU83%EfPlk6ppY~?lYQ+eEf6Hj zJIfqk1#o|#+Kx{b9LV3u6^0x&UaS_j@4<2b7#0r1jfhF5HSqNa{>Bpd+*fY^V1@N2 zxl*k44Z&!0^fA2LtJzC7Nc2Ng*YZrS+s$+3Y?7Z z@R5`_i+(Piqq%}aypLqh8E8XIOKH=J+kvTU43F;Ph`f=BLc9p(Gcs&Iw~2646+WN? zzRj9*G9dQX%Qrz@Bh+01{NObvW|2m-7CT~4bLI{6vt8AD-ASzDuGZZYrr%TBq7JLV zi7MT5Iv35Wx64VyNEDq+m%DZrAC0xMtF$@g5Ov`zBdD}aB0(%GBgc_TNgIt9=4cA% znUb{epv)0e?S${Z%Q)s5MARrHh14(YPl~mgeDE}ro+u%P=vy!@nkRNih3qHDn=^?w z3Jm-9!jB|d*kh_d0~RnBc9r2=lnhb@gbP%IebFnRJj6>sv^Blb`W{ppTmNyz~6{oRh<(+t$`@@f$(&S}h#4 z!j?vt3XjT-*!Kx?hyEPgigwQ}rN?MxrkJ`t9(~#BhlyDU%BS-0scCrDAgoi3fLtb+ z!$9QE>qFACYD(cF`vMV*`c3=v;De;m3Fq~(1cArg$0?8rV8}QL>Fkn&Ksqs+Er(Gb za{oD}eoYy2dcyQYLOfmJ_0xVcjY3YllE)*mMRpC}8T>GkRQw=cQ-=e{*0tI`%kk!z zPgrw9!ozFlb`a7(bU80kXi@2S31$dp#AZAVCc@(W z@}#2Zha*M+=4O6su3_7ve0SDmrqM9nu$(;mLOMb0UJEzk&BkC44JY0A3Ei<_gVP}i zrfHQO*J}P*x3JLl5Q#LEp(@y9+&bUasi)=i?R@nbnw5!MZ_N$beejc>1VWn0r58sL zH-su~m6kX!ZA7gK#XJGExR81Txd-hCITx`O{Lw7Yj@EDyal|!{+!yL&`QW%ZjXHI0 z>w>f+0bLODwEC5Yy#CriO5b2_R~VP0nQqU7BPg#3QcbIj6G$^H(#_lLKF;9j{O$ea z;yQ7|w=8yQIW~wfwNLg5Q8K^xNwVqHMZQ`}axm0ORl!pR!p*DNk17 z&qFyiVIp*f{p;4r2{9F}R5zt)>#4;3WS`Q|lvCy96|XLe;wfxmz2y(|XDIO@5(j->907vU3!;aiECN- z^uf9_&A}8Q-fecZzBflXcw3L`dnNs%AW&;HZab(;`Amh8yGeDsQm>R+pfK4?vL*_j z)G+>e;f>#%d{JggaV2|DruO==cioPf6rPY7VMl(yKt{Yc_90v9nc_49cAN@P=o9K> zlM6MYzBi;2;8@Cc)w}&1sd!o8R$$cPp69}*RdC;pM~aLH3N~QBL?)Z^I%PCJ4b@2f z>TMQ$_O%lvbgSn1TiS0%Y#wbv)vXGp9p?{~+nMOv8X73u+nQP$+uczP8kLdnWP1P@ z7Qk`}u40M1@gJreU=mITpkvk=5fnK{Zy$aTpckd&IvHhkB%-Y>D0g{D8cip@ZSO!= z3%k9Hx&L)zGHI;|5gaUl?iq@daf2{Q>*S^%qV`=QS3au0Z{*RYWU>J9H`rv=dA(GD zHMR^iX5=ejO2ANDQQzTwzu>#q9&cjo7QPyG?YFis!>8vhGBx1{F`7@QS9jr;z?;LF zms{*_m*}asRu&st8CV*Yh0gKaaO1bcjA^Enh!Pbheu()ncQnTBDc(g%=PUT_ooCk8 z#p%u3!J|=@ft9Ew&)mAj6-mgg%FW43h>1b&LDlZ0vB9(XIc4+~H3bDut4yPnZwK2V=^$Aa9s2qo zg!D|@5Z-O+^R2Uhn0<&FR2J1t;Aqz2v)MR3)Q9vgr#$kfmlsy!NpRyoiBi#nnlGq$e1}~ ziN=)JelxRt-;xkiC6hWdc8J3{TlzXzOhS&(#rktUQzuc|!~)gOwyR)higPB#xxZ!d z=s|)i*A73;B^%~9DC9I1_U!D=s`#d&yp|x&S=!as_Q)@W1<8Q88h4XbQ3SlUgmL(R z%l7z+K(UjUNz7`&1=;!KN@y@ zXc;%|Vw~m4pFE@(YKs4O6bJi3I@r}(7~KlKV5>TxPg|p+7n@A$sBd6Ey~LU}+~Nda zuZAJx0%4tKfZVFRe>+D%Or`W0)pIazMo?B+err#u<+oDDK7eEEgzhFg+<%NPKO2$o zI1#IK=&W-u;ewQDp*nEDGPcZ{4mET}EU(zi%}EEq*qvWjJ$rMBK72A$BCyiwEO>rC zFcvLv^O%ayyHd0yJ-@^sOR?6{f7ABjsOM+^+9cYHTNmN39J8ujsJUN76W6-b3V2f% zz5~g-t#uRY*X`;qI_60#N@-Xi?CzU9HIf82r?8^Mg5y>q(caKC(rx#=*EKexlCB;!%z7>!9Lst3L9lJimwfh`aZ%DSt!PtM&pCEY4`JbO?^@D zsJ_HeEuHZRBfng&Y3yP097M_ix0`nr!gXr? z6fTIBF4TqiTre+XmwWwvW~#ze*RwH$Ad2287}Ap4YL8E&$qD3|J*6nqVB-fK&YW8% zMipBFpsTLwbwPt!=yy$F>bO;ILlm6#sslQcX)gQt_?+aIIMo~m8xpxHa6>829l2F^ zbS7tCuwpGp?h~DZ>cST#N*kJew)rNzhO7Mg@FJT1%D>u+s#9c3u=b)jO)$yuOv*>u zImz^fIm%-cC`WyyS*)XGO#e7OlYu^#d70I6hWNWw&dL+f$`9T7yCC-vI#V*R(pR*# zvbJN@x3V?7i*RH){~IMi;~M}v8X#5~7NvwoV4f-bX4n2Z_td|4S2W%j=RebcsPuX0MBFe6=#UK!@Vkq z7u8b-D`AJ=jvhF*O@)iwi=qfp3>1LfycZvtDZXpzpdvr(*@7_qSo3?E!kcg`iS78b zE29dYv~$G6+Lo)%BCST9lyZgk%;NfZw46X@ZY~taU559Kx6?Cle&+0Lqzez0&DmgA_D8l@!t$KymmsnjU_UQ~4!K#5 z5exMUK^+1u6XNUQtcis;dLGOzVDT9&A;Vl`i*CUt)SF1HODk?Uigj++`-wjzihXSz+RYbHlQZ*5jym zE0x(K5PLDrM@fn$iL1lYx$E1rK9%~!3ZCC_MJo$&Jk^GBxwQM0bqVDW8yN?m(VT`8_$##Mt;R7z&u_>szPA&WrKvRYzy*b5agSGVBTIYc`gwv1B}p6)wLWQCv?F}>TWcEL z>FL|YUf%%49!-{aWf!d2%$?9ecj6BL!@x2_i@@KX`2K6d{k0p@*x z`wiqTfCJ2b|N1}L$@?hx`=VbcI#|C&`Pn7iN4a0-|3aCBj$@%r{G;fcss + + text/csv + CSV + Comma-Separated Values + 1 + false + csv + + application/msword Microsoft Word From 476fe7220dd2ed47b4a2ec808a3e37d53d41e154 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 30 Mar 2022 14:43:29 -0500 Subject: [PATCH 0823/1254] Minor fix to BitstreamFormatRestRepositoryIT after adding new format (CSV) --- .../org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java index 48ad410d00..1a6cc29ca7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java @@ -56,7 +56,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati @Autowired private BitstreamFormatConverter bitstreamFormatConverter; - private final int DEFAULT_AMOUNT_FORMATS = 80; + private final int DEFAULT_AMOUNT_FORMATS = 81; @Test public void findAllPaginationTest() throws Exception { From 9ef561a5d01189f37b6dcc37bb350a3981b40f58 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 30 Mar 2022 16:52:39 -0500 Subject: [PATCH 0824/1254] Switch to System.err.format & correct spacing in error messages. --- .../dspace/app/mediafilter/TikaTextExtractionFilter.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java index 909291e450..07b1576086 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java @@ -79,14 +79,13 @@ public class TikaTextExtractionFilter tika.setMaxStringLength(maxChars); // Tell Tika the maximum number of characters to extract extractedText = tika.parseToString(source); } catch (IOException e) { - System.err.println("Unable to extract text from bitstream in Item " + currentItem.getID().toString()); + System.err.format("Unable to extract text from bitstream in Item %s%n", currentItem.getID().toString()); e.printStackTrace(); log.error("Unable to extract text from bitstream in Item {}", currentItem.getID().toString(), e); throw e; } catch (OutOfMemoryError oe) { - System.err.println("OutOfMemoryError occurred when extracting text from bitstream in Item " + - currentItem.getID().toString() + - "You may wish to enable 'textextractor.use-temp-file'."); + System.err.format("OutOfMemoryError occurred when extracting text from bitstream in Item %s. " + + "You may wish to enable 'textextractor.use-temp-file'.%n", currentItem.getID().toString()); oe.printStackTrace(); log.error("OutOfMemoryError occurred when extracting text from bitstream in Item {}. " + "You may wish to enable 'textextractor.use-temp-file'.", currentItem.getID().toString(), oe); From b5876852d90b7f07f25d9d1eb0228460b1686182 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 4 Apr 2022 10:28:04 -0500 Subject: [PATCH 0825/1254] Resolve feedback. Ensure errors are re-thrown & fix comment. --- .../mediafilter/TikaTextExtractionFilter.java | 18 ++++++++++++------ .../TikaTextExtractionFilterTest.java | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java index 07b1576086..e83bf706ed 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java @@ -136,12 +136,15 @@ public class TikaTextExtractionFilter * Write all extracted characters directly to the temp file. */ @Override - public void characters(char[] ch, int start, int length) { + public void characters(char[] ch, int start, int length) throws SAXException { try { writer.append(new String(ch), start, length); } catch (IOException e) { - log.error("Could not append to temporary file at {} when performing text extraction", - tempExtractedTextFile.getAbsolutePath(), e); + String errorMsg = String.format("Could not append to temporary file at %s " + + "when performing text extraction", + tempExtractedTextFile.getAbsolutePath()); + log.error(errorMsg, e); + throw new SAXException(errorMsg, e); } } @@ -151,12 +154,15 @@ public class TikaTextExtractionFilter * (like blank lines, indentations, etc.), so that we get the same extracted text either way. */ @Override - public void ignorableWhitespace(char[] ch, int start, int length) { + public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { try { writer.append(new String(ch), start, length); } catch (IOException e) { - log.error("Could not append to temporary file at {} when performing text extraction", - tempExtractedTextFile.getAbsolutePath(), e); + String errorMsg = String.format("Could not append to temporary file at %s " + + "when performing text extraction", + tempExtractedTextFile.getAbsolutePath()); + log.error(errorMsg, e); + throw new SAXException(errorMsg, e); } } }); diff --git a/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java b/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java index b7d0ed5a3b..9db1ef7776 100644 --- a/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java +++ b/dspace-api/src/test/java/org/dspace/app/mediafilter/TikaTextExtractionFilterTest.java @@ -23,7 +23,7 @@ import org.junit.Test; /** * Test the TikaTextExtractionFilter using test files for all major formats. - * The test files used below are all located at [dspace-api]/src/main/resources/org/dspace/app/mediafilter/ + * The test files used below are all located at [dspace-api]/src/test/resources/org/dspace/app/mediafilter/ * * @author mwood * @author Tim Donohue From 3a01dbe0a2b1f4ca6bfe30f2f785b9c6c55ee6fb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 20 Dec 2021 16:50:00 -0600 Subject: [PATCH 0826/1254] Upgrade to latest Spring Boot, Solr & Postgres. Minor code fixes/dependency updates to get everything to compile --- dspace-server-webapp/pom.xml | 8 ++++-- .../rest/AuthenticationRestController.java | 2 +- .../app/rest/DiscoveryRestController.java | 2 +- .../app/rest/IdentifierRestController.java | 4 +-- .../dspace/app/rest/OidcRestController.java | 2 +- .../app/rest/RestResourceController.java | 4 +-- .../app/rest/StatisticsRestController.java | 2 +- .../SubmissionCCLicenseUrlRepository.java | 2 +- .../app/rest/UUIDLookupRestController.java | 4 +-- .../dspace/app/rest/link/HalLinkFactory.java | 4 +-- .../repository/ClaimedTaskRestRepository.java | 2 +- .../rest/repository/DSpaceRestRepository.java | 8 ++++++ .../repository/EPersonRestRepository.java | 2 +- .../repository/PoolTaskRestRepository.java | 2 +- .../ResourcePolicyRestRepository.java | 2 +- .../VocabularyEntryDetailsRestRepository.java | 2 +- pom.xml | 28 ++++++++++++++----- 17 files changed, 52 insertions(+), 28 deletions(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 4677366a2c..6c34e11a81 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -298,6 +298,12 @@ org.springframework.data spring-data-rest-hal-browser ${spring-hal-browser.version} + + + org.springframework.data + spring-data-rest-webmvc + + @@ -463,13 +469,11 @@ com.jayway.jsonpath json-path - ${json-path.version} test com.jayway.jsonpath json-path-assert - ${json-path.version} test 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 9660d0af56..7d9cb470f9 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 @@ -80,7 +80,7 @@ public class AuthenticationRestController implements InitializingBean { @Override public void afterPropertiesSet() { discoverableEndpointsService - .register(this, Arrays.asList(new Link("/api/" + AuthnRest.CATEGORY, AuthnRest.NAME))); + .register(this, Arrays.asList(Link.of("/api/" + AuthnRest.CATEGORY, AuthnRest.NAME))); } @RequestMapping(method = RequestMethod.GET) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java index 5ecbe19176..947515ca54 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java @@ -73,7 +73,7 @@ public class DiscoveryRestController implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { discoverableEndpointsService - .register(this, Arrays.asList(new Link("/api/" + SearchResultsRest.CATEGORY, SearchResultsRest.CATEGORY))); + .register(this, Arrays.asList(Link.of("/api/" + SearchResultsRest.CATEGORY, SearchResultsRest.CATEGORY))); } @RequestMapping(method = RequestMethod.GET) 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 d3a6ef981d..5f7a473b91 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 @@ -66,8 +66,8 @@ public class IdentifierRestController implements InitializingBean { discoverableEndpointsService .register(this, Arrays.asList( - new Link( - new UriTemplate("/api/" + CATEGORY + "/" + ACTION, + Link.of( + UriTemplate.of("/api/" + CATEGORY + "/" + ACTION, new TemplateVariables( new TemplateVariable(PARAM, VariableType.REQUEST_PARAM))), CATEGORY))); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java index ab34a72a8d..db04b3b7cd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OidcRestController.java @@ -45,7 +45,7 @@ public class OidcRestController { @PostConstruct public void afterPropertiesSet() { - discoverableEndpointsService.register(this, List.of(new Link("/api/" + AuthnRest.CATEGORY, "oidc"))); + discoverableEndpointsService.register(this, List.of(Link.of("/api/" + AuthnRest.CATEGORY, "oidc"))); } @RequestMapping(method = RequestMethod.GET) 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 7c79a85701..b82b483075 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 @@ -127,7 +127,7 @@ public class RestResourceController implements InitializingBean { // Link l = linkTo(this.getClass(), r).withRel(r); String[] split = r.split("\\.", 2); String plural = English.plural(split[1]); - Link l = new Link("/api/" + split[0] + "/" + plural, plural); + Link l = Link.of("/api/" + split[0] + "/" + plural, plural); links.add(l); log.debug(l.getRel().value() + " " + l.getHref()); } @@ -821,7 +821,7 @@ public class RestResourceController implements InitializingBean { link = linkTo(this.getClass(), apiCategory, model).slash(uuid).slash(subpath).withSelfRel(); } - return new EntityModel(new EmbeddedPage(link.getHref(), + return EntityModel.of(new EmbeddedPage(link.getHref(), pageResult.map(converter::toResource), null, subpath)); } else { RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java index 4c2dc54574..a491157f53 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java @@ -57,7 +57,7 @@ public class StatisticsRestController implements InitializingBean { public void afterPropertiesSet() throws Exception { discoverableEndpointsService .register(this, Arrays - .asList(new Link("/api/" + RestAddressableModel.STATISTICS, RestAddressableModel.STATISTICS))); + .asList(Link.of("/api/" + RestAddressableModel.STATISTICS, RestAddressableModel.STATISTICS))); } @RequestMapping(method = RequestMethod.GET) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java index 78e71d91fd..c32d551cbe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java @@ -132,7 +132,7 @@ public class SubmissionCCLicenseUrlRepository extends DSpaceRestRepository { } protected Link buildLink(String rel, String href) { - Link link = new Link(href, rel); - - return link; + return Link.of(href, rel); } protected CONTROLLER getMethodOn(Object... parameters) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java index 3c0c81a88a..dbd37093a9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java @@ -287,7 +287,7 @@ public class ClaimedTaskRestRepository extends DSpaceRestRepository ids) { + // TODO Auto-generated method stub + } + @Override /** * Method to implement to support bulk delete of ALL entity instances diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index a1cd6dd25f..cfae12584f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -336,6 +336,6 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository LYRASIS - http://www.dspace.org + https://dspace.org 11 - 5.2.5.RELEASE - 2.2.6.RELEASE - 5.2.2.RELEASE + 5.3.15 + 2.6.3 + 5.6.1 5.6.5.Final 6.0.23.Final 42.3.3 @@ -47,9 +47,9 @@ - 3.2.6.RELEASE + 3.3.9.RELEASE - 2.4.0 + 2.6.0 7.9 @@ -1738,7 +1738,7 @@ com.google.guava guava - 30.1.1-jre + 31.0.1-jre @@ -1752,6 +1752,20 @@ xom 1.2.5 + + + com.jayway.jsonpath + json-path + ${json-path.version} + test + + + com.jayway.jsonpath + json-path-assert + ${json-path.version} + test + + javax.xml.bind From d8e4a569d6dbba9502ac4083abaca067dd24a407 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 21 Dec 2021 12:02:00 -0600 Subject: [PATCH 0827/1254] Fix circular reference errors in beans. Use @Lazy annotation for beans which must be loaded later / on first use. --- .../converter/AInprogressItemConverter.java | 3 + .../rest/converter/BitstreamConverter.java | 4 -- .../rest/converter/ClaimedTaskConverter.java | 3 + .../app/rest/converter/ConverterService.java | 6 +- .../rest/converter/DSpaceObjectConverter.java | 3 + .../HarvestedCollectionConverter.java | 3 + .../app/rest/converter/MetadataConverter.java | 3 + .../converter/MetadataFieldConverter.java | 3 + .../app/rest/converter/PoolTaskConverter.java | 3 + .../app/rest/converter/ProcessConverter.java | 3 + .../rest/converter/RelationshipConverter.java | 3 + .../converter/RelationshipTypeConverter.java | 3 + .../converter/ResourcePolicyConverter.java | 3 + .../SubmissionCCLicenseConverter.java | 3 + .../SubmissionCCLicenseFieldConverter.java | 3 + .../SubmissionDefinitionConverter.java | 3 + .../rest/converter/TemplateItemConverter.java | 3 + .../WorkflowDefinitionConverter.java | 3 + .../rest/converter/WorkflowStepConverter.java | 3 + .../exception/DSpaceAccessDeniedHandler.java | 8 +-- .../AbstractDSpaceRestRepository.java | 2 + .../rest/repository/DSpaceRestRepository.java | 57 ++++++++++++++----- .../security/WebSecurityConfiguration.java | 10 +++- ...JWTTokenRestAuthenticationServiceImpl.java | 8 +-- .../app/rest/submit/SubmissionService.java | 2 + .../dspace/app/rest/utils/AuthorityUtils.java | 4 ++ .../java/org/dspace/app/rest/utils/Utils.java | 3 + 27 files changed, 122 insertions(+), 33 deletions(-) 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 1fb2bffc58..ce7ca34918 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 @@ -27,6 +27,7 @@ import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; /** * Abstract implementation providing the common functionalities for all the inprogressSubmission Converter @@ -44,6 +45,8 @@ public abstract class AInprogressItemConverter { - @Autowired - ConverterService converter; - @Override public BitstreamRest convert(org.dspace.content.Bitstream obj, Projection projection) { BitstreamRest b = super.convert(obj, projection); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java index cc7459955f..5238b8888a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java @@ -14,6 +14,7 @@ import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -26,6 +27,8 @@ import org.springframework.stereotype.Component; public class ClaimedTaskConverter implements IndexableObjectConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java index 18fc119eef..0f7b47239e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java @@ -38,6 +38,7 @@ import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.context.annotation.Lazy; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.data.domain.Page; @@ -52,9 +53,12 @@ import org.springframework.stereotype.Service; /** * Converts domain objects from the DSpace service layer to rest objects, and from rest objects to resource * objects, applying {@link Projection}s where applicable. - * + *

    + * MUST be loaded @Lazy, as this service requires other services to be preloaded (especially DSpaceConverter components) + * and that can result in circular references if those services need this ConverterService (and many do). * @author Luca Giamminonni (luca.giamminonni at 4science dot it) */ +@Lazy @Service public class ConverterService { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java index 0c730265ef..a89d5ec4e9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java @@ -24,6 +24,7 @@ import org.dspace.content.MetadataValue; import org.dspace.core.Context; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; /** * This is the base converter from/to objects in the DSpace API data model and @@ -38,6 +39,8 @@ public abstract class DSpaceObjectConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java index 14189a4401..76aca4be23 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java @@ -28,6 +28,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -39,6 +40,8 @@ public class MetadataConverter implements DSpaceConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java index 48299dd362..548dc63753 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java @@ -13,6 +13,7 @@ import org.dspace.discovery.IndexableObject; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -25,6 +26,8 @@ import org.springframework.stereotype.Component; public class PoolTaskConverter implements IndexableObjectConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java index 4a794c9b85..de03d60630 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java @@ -15,6 +15,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.scripts.Process; import org.dspace.scripts.service.ProcessService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -23,6 +24,8 @@ import org.springframework.stereotype.Component; @Component public class ProcessConverter implements DSpaceConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java index f9d0cf52ec..1459db1a94 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java @@ -11,6 +11,7 @@ import org.dspace.app.rest.model.RelationshipRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.Relationship; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -20,6 +21,8 @@ import org.springframework.stereotype.Component; @Component public class RelationshipConverter implements DSpaceConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java index c20249c025..44a5eb3d13 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java @@ -11,6 +11,7 @@ import org.dspace.app.rest.model.RelationshipTypeRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.RelationshipType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -20,6 +21,8 @@ import org.springframework.stereotype.Component; @Component public class RelationshipTypeConverter implements DSpaceConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java index ab8694874c..6b233fed8b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java @@ -12,6 +12,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -26,6 +27,8 @@ public class ResourcePolicyConverter implements DSpaceConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldConverter.java index 782056dc1c..70a1a4d76c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldConverter.java @@ -16,6 +16,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.license.CCLicenseField; import org.dspace.license.CCLicenseFieldEnum; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -27,6 +28,8 @@ import org.springframework.stereotype.Component; public class SubmissionCCLicenseFieldConverter implements DSpaceConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired private ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java index f380a6695f..eccd9cba41 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java @@ -27,6 +27,7 @@ import org.dspace.content.Collection; import org.dspace.core.Context; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -47,6 +48,8 @@ public class SubmissionDefinitionConverter implements DSpaceConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowDefinitionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowDefinitionConverter.java index 04af851e8b..e7f322a20c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowDefinitionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowDefinitionConverter.java @@ -15,6 +15,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.state.Workflow; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** @@ -28,6 +29,8 @@ public class WorkflowDefinitionConverter implements DSpaceConverter { + // Must be loaded @Lazy, as ConverterService autowires all DSpaceConverter components + @Lazy @Autowired ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceAccessDeniedHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceAccessDeniedHandler.java index 9e20eca4c2..c2842d33d2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceAccessDeniedHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceAccessDeniedHandler.java @@ -12,9 +12,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.dspace.app.rest.security.WebSecurityConfiguration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Lazy; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.csrf.CsrfToken; @@ -40,8 +40,9 @@ import org.springframework.web.servlet.HandlerExceptionResolver; @Component public class DSpaceAccessDeniedHandler implements AccessDeniedHandler { + @Lazy @Autowired - private WebSecurityConfiguration webSecurityConfiguration; + private CsrfTokenRepository csrfTokenRepository; @Autowired @Qualifier("handlerExceptionResolver") @@ -69,9 +70,6 @@ public class DSpaceAccessDeniedHandler implements AccessDeniedHandler { // switched clients (from HAL Browser to UI or visa versa) and has an out-of-sync token. // NOTE: this logic is tested in AuthenticationRestControllerIT.testRefreshTokenWithInvalidCSRF() if (ex instanceof InvalidCsrfTokenException) { - // Get access to our enabled CSRF token repository - CsrfTokenRepository csrfTokenRepository = webSecurityConfiguration.getCsrfTokenRepository(); - // Remove current token & generate a new one csrfTokenRepository.saveToken(null, request, response); CsrfToken newToken = csrfTokenRepository.generateToken(request); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java index f835098d55..0ccfa5249f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java @@ -14,6 +14,7 @@ import org.dspace.core.Context; import org.dspace.services.RequestService; import org.dspace.utils.DSpace; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; /** * This is the base class for any Rest Repository. It provides utility method to @@ -26,6 +27,7 @@ public abstract class AbstractDSpaceRestRepository { @Autowired protected Utils utils; + @Lazy @Autowired protected ConverterService converter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index a6cec26015..01f127eca5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -22,12 +22,15 @@ import org.dspace.app.rest.exception.DSpaceBadRequestException; 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.ItemRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.patch.Patch; import org.dspace.authorize.AuthorizeException; import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; +import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -45,20 +48,44 @@ import org.springframework.web.multipart.MultipartFile; */ public abstract class DSpaceRestRepository extends AbstractDSpaceRestRepository - implements PagingAndSortingRepository { + implements PagingAndSortingRepository, BeanNameAware { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceRestRepository.class); - //Trick to make inner-calls to ourselves that are checked by Spring security - //See: - // https://stackoverflow.com/questions/13564627/spring-aop-not-working-for-method-call-inside-another-method - // https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#aop-understanding-aop-proxies - @Autowired + private String thisRepositoryBeanName; private DSpaceRestRepository thisRepository; + @Autowired + private ApplicationContext applicationContext; + @Autowired private MetadataFieldService metadataFieldService; + /** + * From BeanNameAware. Allows us to capture the name of the bean, so we can load it into thisRepository. + * See getThisRepository() method. + * @param beanName name of ourselves + */ + @Override + public void setBeanName(String beanName) { + this.thisRepositoryBeanName = beanName; + } + + /** + * Get access to our current DSpaceRestRepository bean. This is a trick to make inner-calls to ourselves that are + * checked by Spring Security + * See: + * https://stackoverflow.com/questions/13564627/spring-aop-not-working-for-method-call-inside-another-method + * https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-understanding-aop-proxies + * @return current DSpaceRestRepository + */ + private DSpaceRestRepository getThisRepository() { + if (thisRepository == null) { + thisRepository = (DSpaceRestRepository) applicationContext.getBean(thisRepositoryBeanName); + } + return thisRepository; + } + @Override public S save(S entity) { Context context = null; @@ -108,7 +135,7 @@ public abstract class DSpaceRestRepository findById(ID id) { Context context = obtainContext(); - final T object = thisRepository.findOne(context, id); + final T object = getThisRepository().findOne(context, id); if (object == null) { return Optional.empty(); } else { @@ -171,7 +198,7 @@ public abstract class DSpaceRestRepository findAll(Pageable pageable) { Context context = obtainContext(); - return thisRepository.findAll(context, pageable); + return getThisRepository().findAll(context, pageable); } /** @@ -271,7 +298,7 @@ public abstract class DSpaceRestRepositoryoai .andExpect(xpath("OAI-PMH/Identify/description/oai-identifier/scheme").string("oai")) // Expect protocol version 2.0 From 7d396434c271c678d616152d82eb05bdd1189449 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 16 Feb 2022 13:44:49 -0600 Subject: [PATCH 0829/1254] Fix UTF-8 encoding issues in Spring Boot by updating config names --- .../src/main/resources/application.properties | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/resources/application.properties b/dspace-server-webapp/src/main/resources/application.properties index 91e2aa73ae..5c1790f031 100644 --- a/dspace-server-webapp/src/main/resources/application.properties +++ b/dspace-server-webapp/src/main/resources/application.properties @@ -60,11 +60,9 @@ spring.messages.encoding=UTF-8 # # # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly. -spring.http.encoding.charset=UTF-8 -# Enable http encoding support. -spring.http.encoding.enabled=true +server.servlet.encoding.charset=UTF-8 # Force the encoding to the configured charset on HTTP requests and responses. -spring.http.encoding.force=true +server.servlet.encoding.force=true ########################### # Server Properties From 5ea14a86370d16522f11fc7e86f1f89365915c5d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 16 Feb 2022 17:08:14 -0600 Subject: [PATCH 0830/1254] Add missing UTF-8 charset to tests --- .../app/opensearch/OpenSearchControllerDisabledIT.java | 6 +++--- .../org/dspace/app/rest/BitstreamRestControllerIT.java | 8 ++++---- .../java/org/dspace/app/rest/SitemapRestControllerIT.java | 8 ++++---- .../dspace/app/rest/WorkspaceItemRestRepositoryIT.java | 4 ++-- .../app/rest/test/AbstractControllerIntegrationTest.java | 4 +++- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerDisabledIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerDisabledIT.java index a757ecbca3..6132544c61 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerDisabledIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerDisabledIT.java @@ -40,8 +40,8 @@ public class OpenSearchControllerDisabledIT extends AbstractControllerIntegratio .param("query", "dog")) //The status has to be 404 Not Found .andExpect(status().isNotFound()) - //We expect the content type to be "application/html" - .andExpect(content().contentType("text/html")) + //We expect the content type to be "text/html" + .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(content().string("OpenSearch Service is disabled")) ; } @@ -52,7 +52,7 @@ public class OpenSearchControllerDisabledIT extends AbstractControllerIntegratio getClient().perform(get("/opensearch/service")) //The status has to be 404 Not Found .andExpect(status().isNotFound()) - .andExpect(content().contentType("text/html")) + .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(content().string("OpenSearch Service is disabled")) ; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index efb7d1776d..ac9c0d2ed6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -203,7 +203,7 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) //We expect the content type to match the bitstream mime type - .andExpect(content().contentType("text/plain")) + .andExpect(content().contentType("text/plain;charset=UTF-8")) //THe bytes of the content must match the original content .andExpect(content().bytes(bitstreamContent.getBytes())); @@ -265,7 +265,7 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest //The response should give us details about the range .andExpect(header().string("Content-Range", "bytes 1-3/10")) //We expect the content type to match the bitstream mime type - .andExpect(content().contentType("text/plain")) + .andExpect(content().contentType("text/plain;charset=UTF-8")) //We only expect the bytes 1, 2 and 3 .andExpect(content().bytes("123".getBytes())); @@ -286,7 +286,7 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest //The response should give us details about the range .andExpect(header().string("Content-Range", "bytes 4-9/10")) //We expect the content type to match the bitstream mime type - .andExpect(content().contentType("text/plain")) + .andExpect(content().contentType("text/plain;charset=UTF-8")) //We all remaining bytes, starting at byte 4 .andExpect(content().bytes("456789".getBytes())); @@ -827,7 +827,7 @@ public class BitstreamRestControllerIT extends AbstractControllerIntegrationTest //The ETag has to be based on the checksum .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) //We expect the content type to match the bitstream mime type - .andExpect(content().contentType("application/pdf")) + .andExpect(content().contentType("application/pdf;charset=UTF-8")) //THe bytes of the content must match the original content .andReturn().getResponse().getContentAsByteArray(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java index fd84aa023b..cbcf970547 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SitemapRestControllerIT.java @@ -107,7 +107,7 @@ public class SitemapRestControllerIT extends AbstractControllerIntegrationTest { //** THEN ** .andExpect(status().isOk()) //We expect the content type to match - .andExpect(content().contentType("text/html")) + .andExpect(content().contentType("text/html;charset=UTF-8")) .andReturn(); String response = result.getResponse().getContentAsString(); @@ -123,7 +123,7 @@ public class SitemapRestControllerIT extends AbstractControllerIntegrationTest { //** THEN ** .andExpect(status().isOk()) //We expect the content type to match - .andExpect(content().contentType("text/html")) + .andExpect(content().contentType("text/html;charset=UTF-8")) .andReturn(); String response = result.getResponse().getContentAsString(); @@ -140,7 +140,7 @@ public class SitemapRestControllerIT extends AbstractControllerIntegrationTest { //** THEN ** .andExpect(status().isOk()) //We expect the content type to match - .andExpect(content().contentType("application/xml")) + .andExpect(content().contentType("application/xml;charset=UTF-8")) .andReturn(); String response = result.getResponse().getContentAsString(); @@ -156,7 +156,7 @@ public class SitemapRestControllerIT extends AbstractControllerIntegrationTest { //** THEN ** .andExpect(status().isOk()) //We expect the content type to match - .andExpect(content().contentType("application/xml")) + .andExpect(content().contentType("application/xml;charset=UTF-8")) .andReturn(); String response = result.getResponse().getContentAsString(); 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 6fa73ddc9e..199313a314 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 @@ -6976,7 +6976,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()) .andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) - .andExpect(content().contentType("text/plain")) + .andExpect(content().contentType("text/plain;charset=UTF-8")) .andExpect(content().bytes(bitstreamContent.getBytes())); // others can't download the bitstream @@ -7014,7 +7014,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isOk()) .andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) - .andExpect(content().contentType("text/plain")) + .andExpect(content().contentType("text/plain;charset=UTF-8")) .andExpect(content().bytes(bitstreamContent.getBytes())); // others can't download the bitstream diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java index a08fd625c4..00339ba2e4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -13,6 +13,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; +import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.Arrays; import java.util.List; @@ -89,8 +90,9 @@ public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWi public static final String REST_SERVER_URL = "http://localhost/api/"; public static final String BASE_REST_SERVER_URL = "http://localhost"; + // Our standard/expected content type protected MediaType contentType = new MediaType(MediaTypes.HAL_JSON.getType(), - MediaTypes.HAL_JSON.getSubtype()); + MediaTypes.HAL_JSON.getSubtype(), StandardCharsets.UTF_8); protected MediaType textUriContentType = RestMediaTypes.TEXT_URI_LIST; From 9e25e1d43e215c208f1ccb0d0c24856615530b9c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 18 Feb 2022 14:04:40 -0600 Subject: [PATCH 0831/1254] Remove duplicative slash "/" in RequestMapping which caused test failures --- .../org/dspace/app/rest/BundleUploadBitstreamController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java index a0f5d5f71e..0cb6bc47e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java @@ -59,7 +59,7 @@ import org.springframework.web.multipart.MultipartFile; * */ @RestController -@RequestMapping("/api/" + BundleRest.CATEGORY + "/" + BundleRest.PLURAL_NAME + "/" +@RequestMapping("/api/" + BundleRest.CATEGORY + "/" + BundleRest.PLURAL_NAME + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/" + BitstreamRest.PLURAL_NAME) public class BundleUploadBitstreamController { From 0311aeb45c3561a37e2248cc98e81da76b815f37 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 18 Feb 2022 16:28:32 -0600 Subject: [PATCH 0832/1254] Ensure files are not required for scripts endpoint. Tests already prove they are not required, but not set that way in controller. --- .../java/org/dspace/app/rest/ScriptProcessesController.java | 6 ++++-- .../dspace/app/rest/repository/ScriptRestRepository.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java index 1197e13c98..196cade5dd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java @@ -55,13 +55,15 @@ public class ScriptProcessesController { * This method can be called by sending a POST request to the system/scripts/{name}/processes endpoint * This will start a process for the script that matches the given name * @param scriptName The name of the script that we want to start a process for + * @param files (Optional) any files that need to be passed to the script for it to run * @return The ProcessResource object for the created process * @throws Exception If something goes wrong */ @RequestMapping(method = RequestMethod.POST) @PreAuthorize("hasAuthority('ADMIN')") - public ResponseEntity> startProcess(@PathVariable(name = "name") String scriptName, - @RequestParam(name = "file") List files) + public ResponseEntity> startProcess( + @PathVariable(name = "name") String scriptName, + @RequestParam(name = "file", required = false) List files) throws Exception { if (log.isTraceEnabled()) { log.trace("Starting Process for Script with name: " + scriptName); 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 eb2afc0e54..096eeedb9e 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 @@ -147,8 +147,10 @@ public class ScriptRestRepository extends DSpaceRestRepository Date: Fri, 18 Feb 2022 16:41:30 -0600 Subject: [PATCH 0833/1254] All multipart/form-data ITs should use multipart(), not post() or fileUpload() --- .../BundleUploadBitstreamControllerIT.java | 14 +++--- .../app/rest/CollectionLogoControllerIT.java | 12 ++--- .../app/rest/CommunityLogoControllerIT.java | 12 ++--- .../app/rest/ScriptRestRepositoryIT.java | 36 ++++++------- .../app/rest/TaskRestRepositoriesIT.java | 8 +-- .../rest/WorkspaceItemRestRepositoryIT.java | 50 +++++++++---------- .../org/dspace/app/rest/csv/CsvExportIT.java | 12 ++--- .../org/dspace/app/rest/csv/CsvImportIT.java | 8 +-- .../org/dspace/curate/CurationScriptIT.java | 47 +++++++---------- 9 files changed, 91 insertions(+), 108 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java index eefcb81656..f80b194ca6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java @@ -103,7 +103,7 @@ public class BundleUploadBitstreamControllerIT extends AbstractEntityIntegration context.restoreAuthSystemState(); MvcResult mvcResult = getClient(token).perform( - MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + MockMvcRequestBuilders.multipart("/api/core/bundles/" + bundle.getID() + "/bitstreams") .file(file) .param("properties", mapper .writeValueAsString(bitstreamRest))) @@ -167,7 +167,7 @@ public class BundleUploadBitstreamControllerIT extends AbstractEntityIntegration input.getBytes()); context.restoreAuthSystemState(); MvcResult mvcResult = getClient(token) - .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .perform(MockMvcRequestBuilders.multipart("/api/core/bundles/" + bundle.getID() + "/bitstreams") .file(file)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.bundleName", is("TESTINGBUNDLE"))) @@ -223,7 +223,7 @@ public class BundleUploadBitstreamControllerIT extends AbstractEntityIntegration context.restoreAuthSystemState(); MvcResult mvcResult = getClient(token) - .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .perform(MockMvcRequestBuilders.multipart("/api/core/bundles/" + bundle.getID() + "/bitstreams") .file(file)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.uuid", notNullValue())).andReturn(); @@ -273,7 +273,7 @@ public class BundleUploadBitstreamControllerIT extends AbstractEntityIntegration context.restoreAuthSystemState(); getClient(token).perform(MockMvcRequestBuilders - .fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .multipart("/api/core/bundles/" + bundle.getID() + "/bitstreams") .file(file)) .andExpect(status().isForbidden()); @@ -315,7 +315,7 @@ public class BundleUploadBitstreamControllerIT extends AbstractEntityIntegration input.getBytes()); context.restoreAuthSystemState(); - getClient().perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + getClient().perform(MockMvcRequestBuilders.multipart("/api/core/bundles/" + bundle.getID() + "/bitstreams") .file(file)) .andExpect(status().isUnauthorized()); @@ -367,7 +367,7 @@ public class BundleUploadBitstreamControllerIT extends AbstractEntityIntegration context.restoreAuthSystemState(); MvcResult mvcResult = getClient(token) - .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .perform(MockMvcRequestBuilders.multipart("/api/core/bundles/" + bundle.getID() + "/bitstreams") .file(file) .param("properties", mapper .writeValueAsString(bitstreamRest))) @@ -422,7 +422,7 @@ public class BundleUploadBitstreamControllerIT extends AbstractEntityIntegration input.getBytes()); context.restoreAuthSystemState(); getClient(token) - .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .perform(MockMvcRequestBuilders.multipart("/api/core/bundles/" + bundle.getID() + "/bitstreams") .file(file).file(file2)) .andExpect(status().isUnprocessableEntity()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java index f093156000..fa0732f6b7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionLogoControllerIT.java @@ -51,7 +51,7 @@ public class CollectionLogoControllerIT extends AbstractControllerIntegrationTes private String createLogoInternal() throws Exception { MvcResult mvcPostResult = getClient(adminAuthToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(childCollection.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(childCollection.getID().toString())) .file(bitstreamFile)) .andExpect(status().isCreated()) .andReturn(); @@ -64,7 +64,7 @@ public class CollectionLogoControllerIT extends AbstractControllerIntegrationTes @Test public void createLogoNotLoggedIn() throws Exception { getClient().perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(childCollection.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(childCollection.getID().toString())) .file(bitstreamFile)) .andExpect(status().isUnauthorized()); } @@ -88,7 +88,7 @@ public class CollectionLogoControllerIT extends AbstractControllerIntegrationTes public void createLogoNoRights() throws Exception { String userToken = getAuthToken(eperson.getEmail(), password); getClient(userToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(childCollection.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(childCollection.getID().toString())) .file(bitstreamFile)) .andExpect(status().isForbidden()); } @@ -96,12 +96,12 @@ public class CollectionLogoControllerIT extends AbstractControllerIntegrationTes @Test public void createDuplicateLogo() throws Exception { getClient(adminAuthToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(childCollection.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(childCollection.getID().toString())) .file(bitstreamFile)) .andExpect(status().isCreated()); getClient(adminAuthToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(childCollection.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(childCollection.getID().toString())) .file(bitstreamFile)) .andExpect(status().isUnprocessableEntity()); } @@ -109,7 +109,7 @@ public class CollectionLogoControllerIT extends AbstractControllerIntegrationTes @Test public void createLogoForNonexisting() throws Exception { getClient(adminAuthToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate("16a4b65b-3b3f-4ef5-8058-ef6f5a653ef9")) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate("16a4b65b-3b3f-4ef5-8058-ef6f5a653ef9")) .file(bitstreamFile)) .andExpect(status().isNotFound()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java index 1d34a99dd9..3a0edc9310 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityLogoControllerIT.java @@ -46,7 +46,7 @@ public class CommunityLogoControllerIT extends AbstractControllerIntegrationTest private String createLogoInternal() throws Exception { MvcResult mvcPostResult = getClient(adminAuthToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(parentCommunity.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(parentCommunity.getID().toString())) .file(bitstreamFile)) .andExpect(status().isCreated()) .andReturn(); @@ -59,7 +59,7 @@ public class CommunityLogoControllerIT extends AbstractControllerIntegrationTest @Test public void createLogoNotLoggedIn() throws Exception { getClient().perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(parentCommunity.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(parentCommunity.getID().toString())) .file(bitstreamFile)) .andExpect(status().isUnauthorized()); } @@ -83,7 +83,7 @@ public class CommunityLogoControllerIT extends AbstractControllerIntegrationTest public void createLogoNoRights() throws Exception { String userToken = getAuthToken(eperson.getEmail(), password); getClient(userToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(parentCommunity.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(parentCommunity.getID().toString())) .file(bitstreamFile)) .andExpect(status().isForbidden()); } @@ -91,12 +91,12 @@ public class CommunityLogoControllerIT extends AbstractControllerIntegrationTest @Test public void createDuplicateLogo() throws Exception { getClient(adminAuthToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(parentCommunity.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(parentCommunity.getID().toString())) .file(bitstreamFile)) .andExpect(status().isCreated()); getClient(adminAuthToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate(parentCommunity.getID().toString())) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate(parentCommunity.getID().toString())) .file(bitstreamFile)) .andExpect(status().isUnprocessableEntity()); } @@ -104,7 +104,7 @@ public class CommunityLogoControllerIT extends AbstractControllerIntegrationTest @Test public void createLogoForNonexisting() throws Exception { getClient(adminAuthToken).perform( - MockMvcRequestBuilders.fileUpload(getLogoUrlTemplate("16a4b65b-3b3f-4ef5-8058-ef6f5a653ef9")) + MockMvcRequestBuilders.multipart(getLogoUrlTemplate("16a4b65b-3b3f-4ef5-8058-ef6f5a653ef9")) .file(bitstreamFile)) .andExpect(status().isNotFound()); } 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 9976fb4be1..da1b5d5c0a 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 @@ -12,8 +12,8 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -211,29 +211,26 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void postProcessNonAdminAuthorizeException() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data")) + getClient(token).perform(multipart("/api/system/scripts/mock-script/processes")) .andExpect(status().isForbidden()); } @Test public void postProcessAnonymousAuthorizeException() throws Exception { - getClient().perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data")) + getClient().perform(multipart("/api/system/scripts/mock-script/processes")) .andExpect(status().isUnauthorized()); } @Test public void postProcessAdminWrongOptionsException() throws Exception { - - String token = getAuthToken(admin.getEmail(), password); AtomicReference idRef = new AtomicReference<>(); try { getClient(token) - .perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data")) + .perform(multipart("/api/system/scripts/mock-script/processes")) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("mock-script", @@ -277,9 +274,8 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { try { getClient(token) - .perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart("/api/system/scripts/mock-script/processes") + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("mock-script", @@ -296,8 +292,7 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { public void postProcessNonExistingScriptNameException() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/system/scripts/mock-script-invalid/processes") - .contentType("multipart/form-data")) + getClient(token).perform(multipart("/api/system/scripts/mock-script-invalid/processes")) .andExpect(status().isBadRequest()); } @@ -323,9 +318,8 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { try { getClient(token) - .perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart("/api/system/scripts/mock-script/processes") + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("mock-script", @@ -361,9 +355,8 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { try { getClient(token) - .perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart("/api/system/scripts/mock-script/processes") + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("mock-script", @@ -468,9 +461,10 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { try { getClient(token) - .perform(fileUpload("/api/system/scripts/mock-script/processes").file(bitstreamFile) - .param("properties", - new Gson().toJson(list))) + .perform(multipart("/api/system/scripts/mock-script/processes") + .file(bitstreamFile) + .characterEncoding("UTF-8") + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("mock-script", 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 d94a16a15d..613ca56fb0 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 @@ -15,8 +15,8 @@ import static org.junit.Assert.assertTrue; import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -2650,7 +2650,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { InputStream bibtex = getClass().getResourceAsStream("bibtex-test.bib"); final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test.bib", "application/x-bibtex", bibtex); - getClient(reviewer1Token).perform(fileUpload("/api/workflow/workflowitems/" + witem.getID()) + getClient(reviewer1Token).perform(multipart("/api/workflow/workflowitems/" + witem.getID()) .file(bibtexFile)) .andExpect(status().isUnprocessableEntity()); @@ -2791,7 +2791,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { InputStream bibtex = getClass().getResourceAsStream("bibtex-test.bib"); final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test.bib", "application/x-bibtex", bibtex); - getClient(authToken).perform(fileUpload("/api/workflow/workflowitems/" + witem.getID()) + getClient(authToken).perform(multipart("/api/workflow/workflowitems/" + witem.getID()) .file(bibtexFile)) .andExpect(status().isUnprocessableEntity()); @@ -2944,7 +2944,7 @@ public class TaskRestRepositoriesIT extends AbstractControllerIntegrationTest { InputStream bibtex = getClass().getResourceAsStream("bibtex-test.bib"); final MockMultipartFile bibtexFile = new MockMultipartFile("file", "bibtex-test.bib", "application/x-bibtex", bibtex); - getClient(reviewer1Token).perform(fileUpload("/api/workflow/workflowitems/" + witem.getID()) + getClient(reviewer1Token).perform(multipart("/api/workflow/workflowitems/" + witem.getID()) .file(bibtexFile)) .andExpect(status().isCreated()); 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 199313a314..91cd50ead3 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 @@ -19,8 +19,8 @@ import static org.hamcrest.Matchers.nullValue; import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -934,7 +934,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration String authToken = getAuthToken(eperson.getEmail(), password); try { // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -962,7 +962,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create a workspaceitem from a single bibliographic entry file explicitly in the col2 try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile) .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) @@ -1026,7 +1026,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create workspaceitems in the default collection (col1) AtomicReference> idRef = new AtomicReference<>(); try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(csvFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -1065,7 +1065,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create workspaceitems explicitly in the col2 try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(csvFile) .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) @@ -1143,7 +1143,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create workspaceitems in the default collection (col1) try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(csvFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -1221,7 +1221,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create workspaceitems in the default collection (col1) try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(tsvFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -1297,7 +1297,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create workspaceitems in the default collection (col1) try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(tsvFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -1374,7 +1374,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration AtomicReference> idRef = new AtomicReference<>(); // create workspaceitems in the default collection (col1) try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(endnoteFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -1453,7 +1453,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create workspaceitems in the default collection (col1) try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(csvFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -1534,7 +1534,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile).file(pubmedFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -1568,7 +1568,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create a workspaceitem from a single bibliographic entry file explicitly in the col2 try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile).file(pubmedFile) .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) @@ -1640,7 +1640,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration String authToken = getAuthToken(eperson.getEmail(), password); // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(bibtexFile)) // create should return return a 422 because we don't allow/support bibliographic files // that have multiple metadata records @@ -1685,7 +1685,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(pubmedFile)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.traditionalpageone['dc.title'][0].value", @@ -1716,7 +1716,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create a workspaceitem from a single bibliographic entry file explicitly in the col2 try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(pubmedFile) .param("owningCollection", col2.getID().toString())) .andExpect(status().isOk()) @@ -1776,7 +1776,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration context.restoreAuthSystemState(); // create a workspaceitem - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(pdfFile)) // create should return 200, 201 (created) is better for single resource .andExpect(status().isOk()) @@ -3491,7 +3491,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration context.restoreAuthSystemState(); // upload the file in our workspaceitem - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems/" + witem.getID()) + getClient(authToken).perform(multipart("/api/submission/workspaceitems/" + witem.getID()) .file(pdfFile)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.sections.upload.files[0].metadata['dc.title'][0].value", @@ -3534,7 +3534,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration context.restoreAuthSystemState(); // upload the file in our workspaceitem - getClient().perform(fileUpload("/api/submission/workspaceitems/" + witem.getID()) + getClient().perform(multipart("/api/submission/workspaceitems/" + witem.getID()) .file(pdfFile)) .andExpect(status().isUnauthorized()); @@ -3580,7 +3580,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // upload the file in our workspaceitem String authToken = getAuthToken(eperson2.getEmail(), "qwerty02"); - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems/" + witem.getID()) + getClient(authToken).perform(multipart("/api/submission/workspaceitems/" + witem.getID()) .file(pdfFile)) .andExpect(status().isForbidden()); @@ -3617,7 +3617,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration context.restoreAuthSystemState(); // upload the file in our workspaceitem - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems/" + witem.getID()) + getClient(authToken).perform(multipart("/api/submission/workspaceitems/" + witem.getID()) .file(pdfFile)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.sections.upload.files[0].metadata['dc.title'][0].value", @@ -4386,7 +4386,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration try { // adding a bibtex file with a single entry should automatically put the metadata in the bibtex file into // the item - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems/" + witem.getID()) + getClient(authToken).perform(multipart("/api/submission/workspaceitems/" + witem.getID()) .file(bibtexFile)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.sections.traditionalpageone['dc.title'][0].value", @@ -4403,7 +4403,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration is("bibtex-test.bib"))); // do again over a submission that already has a title, the manual input should be preserved - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems/" + witem2.getID()) + getClient(authToken).perform(multipart("/api/submission/workspaceitems/" + witem2.getID()) .file(bibtexFile)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.sections.traditionalpageone['dc.title'][0].value", @@ -4944,7 +4944,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // upload file and verify response getClient(authToken) - .perform(fileUpload("/api/submission/workspaceitems/" + wItem.getID()).file(pdfFile)) + .perform(multipart("/api/submission/workspaceitems/" + wItem.getID()).file(pdfFile)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.sections.upload.files[0].accessConditions", empty())); @@ -5415,7 +5415,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration context.restoreAuthSystemState(); try { - getClient(authToken).perform(fileUpload("/api/submission/workspaceitems") + getClient(authToken).perform(multipart("/api/submission/workspaceitems") .file(pdfFile)) .andExpect(status().is(500)); } finally { @@ -7171,7 +7171,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration .andExpect(jsonPath("$.sections.upload.files[1]").doesNotExist()); // upload second file - getClient(tokenEPerson).perform(fileUpload("/api/submission/workspaceitems/" + wItem.getID()) + getClient(tokenEPerson).perform(multipart("/api/submission/workspaceitems/" + wItem.getID()) .file(xmlFile)) .andExpect(status().isCreated()); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvExportIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvExportIT.java index 7439edb498..debd7af20d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvExportIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvExportIT.java @@ -9,7 +9,7 @@ package org.dspace.app.rest.csv; import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.is; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -76,9 +76,8 @@ public class CsvExportIT extends AbstractControllerIntegrationTest { String token = getAuthToken(admin.getEmail(), password); getClient(token) - .perform(fileUpload("/api/system/scripts/metadata-export/processes") - .param("properties", - new Gson().toJson(list))) + .perform(multipart("/api/system/scripts/metadata-export/processes") + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("metadata-export", @@ -128,9 +127,8 @@ public class CsvExportIT extends AbstractControllerIntegrationTest { String token = getAuthToken(admin.getEmail(), password); getClient(token) - .perform(fileUpload("/api/system/scripts/metadata-export/processes") - .param("properties", - new Gson().toJson(list))) + .perform(multipart("/api/system/scripts/metadata-export/processes") + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("metadata-export", diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java index b2eb36215a..59e2eab424 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java @@ -13,8 +13,8 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -284,8 +284,8 @@ public class CsvImportIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); getClient(token) - .perform(fileUpload("/api/system/scripts/metadata-import/processes").file(bitstreamFile) - .param("properties", + .perform(multipart("/api/system/scripts/metadata-import/processes").file(bitstreamFile) + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andDo(result -> idRef @@ -344,7 +344,7 @@ public class CsvImportIT extends AbstractEntityIntegrationTest { String token = getAuthToken(admin.getEmail(), password); getClient(token) - .perform(fileUpload("/api/system/scripts/metadata-import/processes").file(bitstreamFile) + .perform(multipart("/api/system/scripts/metadata-import/processes").file(bitstreamFile) .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java index 66c0319857..0390136af7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java @@ -9,7 +9,7 @@ package org.dspace.curate; import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.is; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -87,9 +87,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { // Request with -t getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) // Illegal Argument Exception .andExpect(status().isBadRequest()); } @@ -109,9 +108,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { // Request with missing required -i getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) // Illegal Argument Exception .andExpect(status().isBadRequest()); } @@ -132,9 +130,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { // Request with missing required -i getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) // Illegal Argument Exception .andExpect(status().isBadRequest()); } @@ -173,9 +170,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { // Request without -t or -T (and no -q ) getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) // Illegal Argument Exception .andExpect(status().isBadRequest()); } @@ -196,9 +192,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { // Request with invalid -s ; must be object, curation or open getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) // Illegal Argument Exception .andExpect(status().isBadRequest()); } @@ -219,9 +214,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { // Request with invalid -s ; must be object, curation or open getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) // Illegal Argument Exception .andExpect(status().isBadRequest()); } @@ -262,9 +256,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { try { getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("curate", @@ -314,9 +307,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { try { getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) .andExpect(status().isAccepted()) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("curate", @@ -366,9 +358,8 @@ public class CurationScriptIT extends AbstractControllerIntegrationTest { try { getClient(token) - .perform(post(CURATE_SCRIPT_ENDPOINT).contentType("multipart/form-data") - .param("properties", - new Gson().toJson(list))) + .perform(multipart(CURATE_SCRIPT_ENDPOINT) + .param("properties", new Gson().toJson(list))) .andExpect(jsonPath("$", is( ProcessMatcher.matchProcess("curate", String.valueOf(admin.getID()), parameters, From 88cea74e43bf905cc5f1aa6add322eb53d7ad8a7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 7 Mar 2022 17:01:02 -0600 Subject: [PATCH 0834/1254] Fix test by ensuring InputStream is closed immediately after usage. Refactor try/finally to support that. --- .../rest/WorkflowItemRestRepositoryIT.java | 88 ++++++++++--------- 1 file changed, 45 insertions(+), 43 deletions(-) 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 6608812791..b44e134d82 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 @@ -1862,7 +1862,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT } @Test - public void whenWorkspaceitemBecomeWorkflowitemWithAccessConditionsTheBitstremMustBeDownloableTest() + public void whenWorkspaceitemBecomeWorkflowitemWithAccessConditionsTheBitstreamMustBeDownloableTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -1890,8 +1890,6 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT String bitstreamContent = "0123456789"; - AtomicReference idRef = new AtomicReference(); - try (InputStream is = IOUtils.toInputStream(bitstreamContent, Charset.defaultCharset())) { context.setCurrentUser(submitter); @@ -1905,56 +1903,60 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT .withDescription("This is a bitstream to test range requests") .withMimeType("text/plain") .build(); + } - context.restoreAuthSystemState(); + context.restoreAuthSystemState(); - String tokenEPerson = getAuthToken(eperson.getEmail(), password); - String tokenSubmitter = getAuthToken(submitter.getEmail(), password); - String tokenReviewer1 = getAuthToken(reviewer1.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String tokenSubmitter = getAuthToken(submitter.getEmail(), password); + String tokenReviewer1 = getAuthToken(reviewer1.getEmail(), password); - // submitter can download the bitstream - getClient(tokenSubmitter).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) - .andExpect(status().isOk()) - .andExpect(header().string("Accept-Ranges", "bytes")) - .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) - .andExpect(content().contentType("text/plain")) - .andExpect(content().bytes(bitstreamContent.getBytes())); + // submitter can download the bitstream + getClient(tokenSubmitter).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()) + .andExpect(header().string("Accept-Ranges", "bytes")) + .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) + .andExpect(content().contentType("text/plain;charset=UTF-8")) + .andExpect(content().bytes(bitstreamContent.getBytes())); - // reviewer can't still download the bitstream - getClient(tokenReviewer1).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) - .andExpect(status().isForbidden()); + // reviewer can't still download the bitstream + getClient(tokenReviewer1).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isForbidden()); - // others can't download the bitstream - getClient(tokenEPerson).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) - .andExpect(status().isForbidden()); + // others can't download the bitstream + getClient(tokenEPerson).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isForbidden()); - // create a list of values to use in add operation - List addAccessCondition = new ArrayList<>(); - List> accessConditions = new ArrayList>(); + // create a list of values to use in add operation + List addAccessCondition = new ArrayList<>(); + List> accessConditions = new ArrayList>(); - Map value = new HashMap<>(); - value.put("name", "administrator"); + Map value = new HashMap<>(); + value.put("name", "administrator"); - accessConditions.add(value); + accessConditions.add(value); - addAccessCondition.add(new AddOperation("/sections/upload/files/0/accessConditions", accessConditions)); + addAccessCondition.add(new AddOperation("/sections/upload/files/0/accessConditions", accessConditions)); - String patchBody = getPatchContent(addAccessCondition); - getClient(tokenSubmitter).perform(patch("/api/submission/workspaceitems/" + witem.getID()) - .content(patchBody) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].name",is("administrator"))) - .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].startDate",nullValue())) - .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].endDate", nullValue())); + String patchBody = getPatchContent(addAccessCondition); + getClient(tokenSubmitter).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].name",is("administrator"))) + .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].startDate",nullValue())) + .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].endDate", nullValue())); - // verify that the patch changes have been persisted - getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + witem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].name",is("administrator"))) - .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].startDate",nullValue())) - .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].endDate", nullValue())); + // verify that the patch changes have been persisted + getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].name",is("administrator"))) + .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].startDate",nullValue())) + .andExpect(jsonPath("$.sections.upload.files[0].accessConditions[0].endDate", nullValue())); + AtomicReference idRef = new AtomicReference(); + + try { // submit the workspaceitem to start the workflow getClient(tokenSubmitter).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") .content("/api/submission/workspaceitems/" + witem.getID()) @@ -1976,7 +1978,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT .andExpect(status().isOk()) .andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) - .andExpect(content().contentType("text/plain")) + .andExpect(content().contentType("text/plain;charset=UTF-8")) .andExpect(content().bytes(bitstreamContent.getBytes())); // submitter can download the bitstream @@ -1984,7 +1986,7 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT .andExpect(status().isOk()) .andExpect(header().string("Accept-Ranges", "bytes")) .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) - .andExpect(content().contentType("text/plain")) + .andExpect(content().contentType("text/plain;charset=UTF-8")) .andExpect(content().bytes(bitstreamContent.getBytes())); // others can't download the bitstream From 54f2c5c661d30c972643c9fd0b7bd43a702f25ed Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 8 Mar 2022 09:39:17 -0600 Subject: [PATCH 0835/1254] Minor updates to Spring dependencies --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index a99306b6aa..7aa5fc9dad 100644 --- a/pom.xml +++ b/pom.xml @@ -19,9 +19,9 @@ 11 - 5.3.15 - 2.6.3 - 5.6.1 + 5.3.16 + 2.6.4 + 5.6.2 5.6.5.Final 6.0.23.Final 42.3.3 From 7a11d0d6051b46fc84aecdde58b2154fcec875c5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 11 Mar 2022 12:16:11 -0600 Subject: [PATCH 0836/1254] Replace Spring Data REST HAL Browser with HAL Browser from WebJars --- dspace-server-webapp/README.md | 5 ++- dspace-server-webapp/pom.xml | 37 +++++-------------- .../java/org/dspace/app/rest/Application.java | 21 ++++++++++- pom.xml | 5 +-- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/dspace-server-webapp/README.md b/dspace-server-webapp/README.md index e71039308a..8d3853e8cc 100644 --- a/dspace-server-webapp/README.md +++ b/dspace-server-webapp/README.md @@ -22,7 +22,10 @@ The only tested way right now is to run this webapp inside your IDE (Eclipse). J > dspace.dir = d:/install/dspace7 ## HAL Browser -The modified version of the HAL Browser from the Spring Data REST project is included, the index.html file is overriden locally to support the /api baseURL (see [DATAREST-971](https://jira.spring.io/browse/DATAREST-971)) + +The modified version of the HAL Browser from https://github.com/mikekelly/hal-browser + +We've updated/customized the HAL Browser to integrate better with our authentication system, provide CSRF support, and use a more recent version of its dependencies. ## Packages and main classes *[org.dspace.app.rest.Application](src/main/java/org/dspace/app/rest/Application.java)* is the spring boot main class it initializes diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 6c34e11a81..e83e411d39 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -238,24 +238,7 @@ - - - - - - + org.springframework.boot spring-boot-starter-web @@ -293,17 +276,15 @@ 0.4.6 - + + + - org.springframework.data - spring-data-rest-hal-browser - ${spring-hal-browser.version} - - - org.springframework.data - spring-data-rest-webmvc - - + org.webjars + hal-browser + ad9b865 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 e0ea0cccb0..459cfe0dee 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 @@ -40,6 +40,7 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** @@ -192,13 +193,31 @@ public class Application extends SpringBootServletInitializer { } } + /** + * Add a ViewController for the root path, to load HAL Browser + * @param registry ViewControllerRegistry + */ + @Override + public void addViewControllers(ViewControllerRegistry registry) { + // Ensure accessing the root path will load the index.html of the HAL Browser + registry.addViewController("/").setViewName("forward:/index.html"); + } + /** * Add a new ResourceHandler to allow us to use WebJars.org to pull in web dependencies - * dynamically for HAL Browser, and access them off the /webjars path. + * dynamically for HAL Browser, etc. * @param registry ResourceHandlerRegistry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { + // First, "mount" the Hal Browser resources at the /browser path + // NOTE: the hal-browser directory uses the version of the Hal browser, so this needs to be synced + // with the org.webjars.hal-browser version in the POM + registry + .addResourceHandler("/browser/**") + .addResourceLocations("/webjars/hal-browser/ad9b865/"); + + // Make all other Webjars available off the /webjars path registry .addResourceHandler("/webjars/**") .addResourceLocations("/webjars/"); diff --git a/pom.xml b/pom.xml index 7aa5fc9dad..9da639b710 100644 --- a/pom.xml +++ b/pom.xml @@ -46,8 +46,6 @@ 1.70 - - 3.3.9.RELEASE 2.6.0 com.jayway.jsonpath json-path ${json-path.version} - test + com.jayway.jsonpath json-path-assert From 92687252ce11f892e13828ba627b86efdc8e11d2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 11 Mar 2022 12:16:44 -0600 Subject: [PATCH 0837/1254] Remove unnecessary config / annotation after Spring upgrades --- .../java/org/dspace/app/rest/utils/ApplicationConfig.java | 2 -- .../src/main/resources/application.properties | 5 ----- 2 files changed, 7 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 7170072e13..c2136781f9 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 @@ -11,7 +11,6 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.data.web.config.EnableSpringDataWebSupport; /** * This class provides extra configuration for our Spring Boot Application @@ -23,7 +22,6 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; * @author Tim Donohue */ @Configuration -@EnableSpringDataWebSupport @ComponentScan( {"org.dspace.app.rest.converter", "org.dspace.app.rest.repository", "org.dspace.app.rest.utils", "org.dspace.app.configuration", "org.dspace.iiif", "org.dspace.app.iiif"}) public class ApplicationConfig { diff --git a/dspace-server-webapp/src/main/resources/application.properties b/dspace-server-webapp/src/main/resources/application.properties index 5c1790f031..5992ded040 100644 --- a/dspace-server-webapp/src/main/resources/application.properties +++ b/dspace-server-webapp/src/main/resources/application.properties @@ -38,11 +38,6 @@ # interact with or read its configuration from dspace.cfg. dspace.dir=${dspace.dir} -######################## -# Spring DATA Rest settings -# -spring.data.rest.basePath= - ######################## # Jackson serialization settings # From cfa9f01a47f0e8648ea17a7b940fc476846cac62 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 14 Mar 2022 12:18:30 -0500 Subject: [PATCH 0838/1254] Upgrade Hal Browser to Bootstrap 4 based on https://github.com/mikekelly/hal-browser/pull/102 . Also add CustomPostForm script from Spring Data REST v3.3.x --- dspace-server-webapp/pom.xml | 7 +- .../src/main/webapp/index.html | 239 ++++++++++-------- .../main/webapp/js/vendor/CustomPostForm.js | 207 +++++++++++++++ .../src/main/webapp/js/vendor/README | 11 + .../src/main/webapp/js/vendor/jsoneditor.js | 14 + .../src/main/webapp/login.html | 26 +- .../src/main/webapp/styles.css | 104 ++++++++ 7 files changed, 487 insertions(+), 121 deletions(-) create mode 100644 dspace-server-webapp/src/main/webapp/js/vendor/CustomPostForm.js create mode 100644 dspace-server-webapp/src/main/webapp/js/vendor/README create mode 100644 dspace-server-webapp/src/main/webapp/js/vendor/jsoneditor.js create mode 100644 dspace-server-webapp/src/main/webapp/styles.css diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index e83e411d39..3d5a922e55 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -68,7 +68,9 @@ src/main/webapp/index.html src/main/webapp/login.html + src/main/webapp/styles.css src/main/webapp/js/hal/** + src/main/webapp/js/vendor/** @@ -302,8 +304,8 @@ toastr 2.1.4 - @@ -312,7 +314,6 @@ 4.5.2 - org.springframework.boot diff --git a/dspace-server-webapp/src/main/webapp/index.html b/dspace-server-webapp/src/main/webapp/index.html index 829c614393..fbab9d93b9 100644 --- a/dspace-server-webapp/src/main/webapp/index.html +++ b/dspace-server-webapp/src/main/webapp/index.html @@ -7,50 +7,53 @@ * * Download file functionality (see new downloadFile() method) * * Improved AuthorizationHeader parsing (see new getAuthorizationHeader() method) * * Upgraded third party dependencies (JQuery) +* * Updated to use Bootstrap 4, based loosely on this PR to HAL Browser: + https://github.com/mikekelly/hal-browser/pull/102 --> - The HAL Browser (customized for DSpace Server Webapp) - + The HAL Browser (customized for DSpace) + - - + -